title: C++基础与面向对象 tags: [嵌入式Linux, Qt, C++, 面向对象, 类与对象, 构造析构, this指针, 继承, 重载, 多态, 虚函数, 封装, 抽象类, IMX6ULL] created: 2026-09-18 updated: 2026-09-18
💡 关联知识:[[05-Qt应用开发/02-Qt入门与项目结构]](Qt 本身就是 C++ 库,
QMainWindow、QPushButton都建立在本文的继承与多态之上)、[[05-Qt应用开发/03-信号与槽]](signals/slots/Q_OBJECT是 Qt 对 C++ 语法的扩展);延伸阅读:[[Linux+C+C++技术体系梳理/1. C语言基础/12. 结构体]](类可以理解为结构体的升级版本)
Qt 是建立在 C++ 之上的。要写出能看懂、能修改的 Qt 程序,不需要成为 C++ 专家,但必须吃透 类与对象、构造析构、this、继承、重载、多态、封装、抽象类 这几块概念——Qt 的每一个类、每一次"重写父类函数"、每一个 ui->setupUi(this) 都用到了它们。
本篇先交代 C++ 与 C 的差异、g++ 编译流程和命名空间,再以一条"狗类"主线把面向对象的核心概念逐个拆开,每个概念都配有 完整可编译的源码。读完即可独立写 C++ 小项目,也能顺畅进入 Qt。
C++(c plus plus)是一种静态类型的、编译式的、通用的、大小写敏感的、不规则的编程语言,同时支持:
C++ 由 Bjarne Stroustrup 于 1979 年在贝尔实验室开始设计,最初命名为"带类的 C",1983 年更名为 C++。C++ 是 C 的超集:任何合法的 C 程序都是合法的 C++ 程序。
标准方面:C++98 是第一个标准,此后陆续有 C++03/11/14/17 等版本(本教程 .pro 中的 CONFIG += c++11 即启用 C++11)。要记住版本原则:固定一个版本,不要随意升级。
学习 C++ 要理解概念,而不是深究语言技术细节。带着本篇的概念去学 Qt,遇到 public、virtual、重写、重载时就能对上号。
原书第二章开篇即说明:指针、数据类型、变量类型、判断、循环等知识在 C 与 C++ 中基本一样,不再重复讲;重点讲 C++ 的新特性和面向对象。差异可归纳如下:
| 对比项 | C | C++ |
| ------ | - | --- |
| 布尔类型 | 旧标准无 bool,用 int 代替 | 原生 bool(true / false) |
| 变量定义位置 | 传统上必须集中在程序(块)开头 | 随用随定义 |
| 初始化方式 | int x = 100; | 还支持 int x(100); 直接初始化 |
| 输入输出 | scanf / printf + 格式符 | cin / cout + << >>,无需格式符 |
| 命名空间 | 无 | 有 namespace,避免命名冲突 |
| 字符串 | char[] + <string.h> | std::string 类型,方便好用 |
| 编程范式 | 过程化 | 过程化 + 面向对象 + 泛型 |
| 数据结构升级 | struct 只有数据 | class = 数据 + 成员函数 + 访问控制 |
| 编译方式 | gcc test.c -o test | g++ test.cpp -o test |
一句话:C 是 C++ 的子集,C++ 在保留 C 能力的同时,把"函数 + 数据"组织成了"类"。
要编译 C++,需要 C 编译器 gcc(可选)和 C++ 编译器 g++。新装的 Ubuntu 默认没有:
# 安装 gcc 与 g++
sudo apt-get install gcc g++
# 安装其他常用库
sudo apt-get install lsb-core lib32stdc++6
# 查看版本,确认安装成功
g++ -v
gcc -v
若编译 Qt 时提示 Cannot find -lGL,还需补装 OpenGL:
sudo apt-get install libglu1-mesa-dev
在终端创建目录并编辑源文件:
mkdir C++ # 创建一个 C++ 目录
cd C++ # 进入该目录
mkdir 01_hello_world # 创建 01_hello_world 目录
cd 01_hello_world # 进入该目录
vi 01_hello_world.cpp # 编辑 cpp 文件,拷贝下面的内容
完整源码:
#include <iostream>
using namespace std;
int main()
{
cout << "Hello, World!" << endl;
return 0;
}
逐行解释:
#include <iostream>:包含标准输入输出流头文件。注意不能写成 iostream.h——带 .h 的是非标准的 C 风格库,不带 .h 的是标准输入输出流,必须配合命名空间使用。using namespace std;:告诉编译器使用标准命名空间 std。cin、cout、endl 都属于 std,不写这句就得写 std::cout。int main():主函数,程序入口。cout << "Hello, World!" << endl;:向屏幕输出并换行。<< 是插入运算符(被重载过),endl 是换行语句。return 0;:终止 main(),并把状态码返回给调用进程。编译与运行:
g++ 01_hello_world.cpp -o 01_hello_world # -o 指定输出的可执行文件名
./01_hello_world # 输出 Hello, World! 并换行
补充:<< 可以连续使用(如 cout << "A\n" << "B" << endl;),也可以直接用 C 的 \n 换行——这说明 C++ 可以直接使用 C 语言语句。
原书只强调两个常用新特性:
bool:C++ 比 C 新增的数据类型。新标准 C 语言也已支持 bool,但旧标准 C 没有,编译器无法解释。变量可随用随定义、可直接初始化:
int x = 100; // 传统写法
int x(100); // C++ 直接初始化,等价于 x = 100
C 语言用 scanf / printf,C++ 用 cin / cout 代替,输入输出流程不变,关键字和用法变了。
C :输入设备 → 输入流 scanf → 变量 → printf → 输出流 → 输出设备
C++:输入设备 → 输入流 cin → 变量 → cout → 输出流 → 输出设备
效率上 scanf / printf 更高,但 cin / cout 更方便。
cout 语法:
cout << x << endl; // x 可为任意数据类型,甚至表达式
cout << x << y; // endl 可省略,仅表示换行
cout << x << y;(变量之间用 <<)cout << x, y << endl;(变量之间不能用逗号)cin 语法:
cin >> x; // x 可为任意数据类型
cin >> x >> y; // 连续输入两个不同变量
using namespace std; 我们已经用过。它的本质是给名字加前缀,避免冲突:
vector 容器,你自己也写了个 vector 类,名字就冲突了。于是标准库把名字都放进 std,你要用就写 std::vector。:: 是作用域运算符,std::cin 读作"std 命名空间下的 cin"。using namespace std; 偷懒。自定义命名空间示例(02_namespace_example.cpp):
#include <iostream>
using namespace std;
namespace A
{
int x = 1;
void fun() {
cout << "A namespace" << endl;
}
}
using namespace A;
int main()
{
fun(); // 声明 using namespace A 后可直接调用
A::x = 3; // 修改 A 命名空间下的 x
cout << A::x << endl;
A::fun(); // 显式指定 A 命名空间下的 fun
return 0;
}
逐行解释:
A,内部有变量 x(初值 1)和函数 fun()。using namespace A; 声明使用命名空间 A。fun();,否则必须写成 A::fun();。A::x 重新赋值为 3。A::x。A::fun()。初学 Qt 时较少看到命名空间,但 Qt 里同样可以自定义命名空间并正常使用。
面向对象的三大特征是 继承、多态、封装。下面以"狗"为主线,把每个概念都配一份例程源码。
类比:把"狗"抽象成一个类(有名字、年龄、会跑会叫),"旺财"就是对象。抽象时只挑出我们关心的几种属性即可。
从类实例化对象有两种方式:
| 方式 | 语法 | 内存位置 | 释放方式 |
|---|---|---|---|
| 栈中实例化 | Dog dog1; |
栈 | 离开作用域自动释放 |
| 堆中实例化 | Dog *dog2 = new Dog(); |
堆 | 必须 delete dog2; |
完整源码(03_class_dog_example.cpp):
#include <iostream>
#include <string>
using namespace std;
class Dog
{
public:
string name;
int age;
void run() {
cout << "小狗的名字是:" << name << "," << "年龄是" << age << endl;
}
};
int main()
{
Dog dog1;
dog1.name = "旺财";
dog1.age = 2;
dog1.run();
Dog *dog2 = new Dog();
if (NULL == dog2) {
return 0;
}
dog2->name = "富贵";
dog2->age = 1;
dog2->run();
delete dog2;
dog2 = NULL;
return 0;
}
逐行解释:
Dog 类,类名尽量贴近它描述的事物,一看就懂。public(公有的)。此外还有 private(私有的)、protected(受保护的)。不写访问限定符默认是 private。简单理解:访问限定符就是给成员变量和成员函数设置访问权限。name、整型变量 age,以及方法 run(),在里面打印狗名和年龄。string 是 C++ 的数据类型,方便好用,使用频率很高。dog1。dog1 的成员赋值,并调用 run()。new 的都是从堆中实例化。new 申请失败则指针为 NULL,程序直接退出,这体现了严谨性。dog2 成员赋值并调用 run()。dog2 重新指向 NULL。类与结构体的关系:类可以说是结构体的升级版本——结构体只有数据,类额外提供成员函数和访问控制。
定义类时,如果没有自己写构造函数和析构函数,编译器会自动生成一个什么也不做的构造函数和析构函数,所以我们平时注意不到。
构造函数:在对象实例化时被系统自动调用,仅且调用一次。特点:
void 也不行。析构函数:与构造函数相反,在对象结束其生命周期时系统自动执行。特点:
~类名();~类名() 不能加参数;void 也不行。完整源码(04_structor_example.cpp):
#include <iostream>
#include <string>
using namespace std;
class Dog
{
public:
Dog();
~Dog();
};
int main()
{
Dog dog;
cout << "构造与析构函数示例" << endl;
return 0;
}
Dog::Dog()
{
cout << "构造函数执行!" << endl;
}
Dog::~Dog()
{
cout << "析构函数执行!" << endl;
}
逐行解释:
Dog 类实例化对象 dog。::。这里把构造函数定义在类外,打印"构造函数执行!"。执行顺序可以预测:对象实例化时先调用构造函数(Dog() 先执行)→ 执行 main() 中的 cout → 对象生命周期结束时调用析构函数。
一个类中不同对象调用自己的成员函数时,其实调用的是同一段函数代码。那么成员函数如何知道要访问哪个对象的数据成员?答案就是 this 指针。
this 指针,this 记录对象的内存地址。this 指针是指向类自身数据的指针,简单说就是指向当前类的当前实例对象。特点:
this 只能在成员函数中使用,全局函数、静态函数都不能使用。实际上成员函数默认第一个参数为 T * const this,即 int func(int p) 在编译器看来原型是 int func(T * const this, int p)。this 在成员函数开始前构造,在成员函数结束后清除。this 的存放位置因编译器而异:可能在栈、寄存器,甚至全局变量。完整源码(05_this_pointer_example.cpp):
#include <iostream>
#include <string>
using namespace std;
class Dog
{
public:
string name;
void func();
};
int main()
{
Dog dog;
dog.func();
return 0;
}
void Dog::func()
{
this->name = "旺财";
cout << "小狗的名字叫:" << this->name << endl;
}
逐行解释:
this 指针指向类成员 name,先赋值为"旺财",再打印。程序输出"小狗的名字叫:旺财"。后面学 Qt 时会经常遇到 this,例如 ui->setupUi(this)、new QPushButton(this)——这里的 this 就是"当前窗口对象自身",作为父对象传给子控件。
继承允许我们依据另一个类来定义一个类,从而达到重用代码、提高执行效率的效果。
Qt 里大量使用继承:当 Qt 的类不满足要求时,就继承它并重写相应函数。
派生类定义形式:
class derived-class : access-specifier base-class
access-specifier 是 public、protected、private 之一;不使用则默认为 private。继承方式:
| 继承方式 | 基类 public 成员 | 基类 protected 成员 | 基类 private 成员 |
|---|---|---|---|
| public 公有继承 | 成为派生类 public | 成为派生类 protected | 不可直接访问(可通过基类 public/protected 成员间接访问) |
| protected 保护继承 | 成为派生类 protected | 成为派生类 protected | 不可直接访问 |
| private 私有继承 | 成为派生类 private | 成为派生类 private | 不可直接访问 |
完整源码(06_inherit_example.cpp,公有继承):
#include <iostream>
#include <string>
using namespace std;
/*动物类,抽象出下面两种属性,
*颜色和体重,是每种动物都具有的属性
*/
class Animal
{
public:
/* 颜色成员变量 */
string color;
/* 体重成员变量 */
int weight;
};
/*让狗类继承这个动物类,并在狗类里写自己的属性。
*狗类拥有自己的属性name,age,run()方法,同时也继承了
*动物类的color 和weight 的属性
*/
class Dog : public Animal
{
public:
string name;
int age;
void run();
};
int main()
{
Dog dog;
dog.name = "旺财";
dog.age = 2;
dog.color = "黑色";
dog.weight = 120;
cout << "狗的名字叫:" << dog.name << endl;
cout << "狗的年龄是:" << dog.age << endl;
cout << "狗的毛发颜色是:" << dog.color << endl;
cout << "狗的体重是:" << dog.weight << endl;
return 0;
}
逐行解释:
Animal 作为基类,Dog 作为派生类,Dog 公有继承了 Animal。dog 对象既使用了自身成员 name、age,又使用了从 Animal 继承来的 color、weight,证明继承生效。C++ 允许在同一作用域中为某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。
同一作用域内可声明几个功能类似的同名函数,但形参(个数、类型或顺序)必须不同。不能仅靠返回类型不同来重载函数。
完整源码(07_func_overloading.cpp):
#include <iostream>
#include <string>
using namespace std;
class Dog
{
public:
string name;
void getWeight(int weight) {
cout << name << "的体重是:" << weight << "kG" << endl;
}
void getWeight(double weight) {
cout << name << "的体重是:" << weight << "kG" << endl;
}
};
int main()
{
Dog dog;
dog.name = "旺财";
dog.getWeight(10);
dog.getWeight(10.5);
return 0;
}
逐行解释:
getWeight(int weight),以 int 类型为参数。getWeight(double weight),参数类型不同,构成函数重载。int 和 double,程序匹配不同的重载函数。注意:如果用户传入的参数类型不在任何重载函数里(例如 string),编译器匹配不到相应重载函数,编译时就会报错。可以多写几种类型(string、char、float 等)覆盖更多情况。
运算符重载的实质就是函数重载或多态,目的是让人能用同名的函数完成不同的基本操作。需要使用特殊函数形式:
<返回类型说明符> operator <运算符符号>(<参数表>)
{
<函数体>
}
可重载的运算符:
| 类别 | 运算符 |
|---|---|
| 双目算术 | + - * / % |
| 关系 | == != < > <= >= |
| 逻辑 | ` |
| 单目 | + - *(指针) &(取地址) |
| 自增自减 | ++ -- |
| 位 | \| & ~ ^ << >> |
| 赋值 | = += -= *= /= %= &= \|= ^= <<= >>= |
| 空间申请与释放 | new delete new[] delete[] |
| 其他 | ()(函数调用) ->(成员访问) , [] |
不可重载的运算符:
| 运算符 | 含义 |
|---|---|
. |
成员访问运算符 |
.* ->* |
成员指针访问运算符 |
:: |
域运算符 |
sizeof |
长度运算符 |
?: |
条件运算符 |
# |
预处理符号 |
以重载 + 为例(08_operator_example.cpp),把两个 Dog 对象的体重相加,返回新的 Dog:
#include <iostream>
#include <string>
using namespace std;
class Dog
{
public:
int weight;
Dog operator+(const Dog &d) {
Dog dog;
dog.weight = this->weight + d.weight;
return dog;
}
};
int main()
{
Dog dog1;
Dog dog2;
Dog dog3;
dog1.weight = 10;
dog2.weight = 20;
dog3 = dog1 + dog2;
cout << "第三只狗的体重是:" << dog3.weight << endl;
return 0;
}
逐行解释:
+ 运算符。成员函数形式下,左操作数是 this,右操作数是参数 d,使用 this->weight 访问左对象成员,返回一个新的 dog 对象。dog1 + dog2 触发重载后的 +,普通算术运算符 + 本来不能把两个对象相加,这正是重载的意义。实际开发中运算符重载用得较少,理解这种思想即可;但 Qt 里 <<(cout 插入)本身就是重载的典型例子。
C++ 多态意味着:调用成员函数时,会根据调用函数的对象的类型执行不同的函数。
形成多态必须具备三个条件:
virtual 声明的函数;在派生类中重新定义基类的虚函数时,会告诉编译器不要静态链接到该函数);两个关键概念:
virtual 声明的函数。声明形式 virtual ReturnType FunctionName(Parameter),虚函数必须实现,不实现编译器会报错。它让编译器不要静态链接,而改为动态链接(后期绑定)。virtual void function1() = 0;。纯虚函数一定没有定义,用来规范派生类的行为(即接口)。包含纯虚函数的类是抽象类,抽象类不能实例化,但可以声明指向实现该抽象类的具体类的指针或引用。完整源码(09_polymorphism_example.cpp):
#include <iostream>
#include <string>
using namespace std;
/* 定义一个动物类 */
class Animal
{
public:
virtual void run() {
cout << "Animal 的run()方法" << endl;
}
};
/* 定义一个狗类,并继承动物类 */
class Dog : public Animal
{
public:
void run() {
cout << "Dog 的run()方法" << endl;
}
};
/* 定义一个猫类,并继承动物类 */
class Cat : public Animal
{
public:
void run() {
cout << "Cat 的run()方法" << endl;
}
};
int main()
{
/* 声明一个Animal 的指针对象,注:并没有实例化 */
Animal *animal;
/* 实例化dog 对象 */
Dog dog;
/* 实例化cat 对象 */
Cat cat;
/* 存储dog 对象的地址 */
animal = &dog;
/* 调用run()方法 */
animal->run();
/* 存储cat 对象的地址 */
animal = &cat;
/* 调用run()方法 */
animal->run();
return 0;
}
逐行解释:
run()。基类 Animal 的 run() 前面加了 virtual,成为虚函数。Animal 声明指针 animal,通过基类指针访问 Dog、Cat 对象的 run()。前提是基类的 run() 必须声明为虚函数;若不声明,基类指针将访问到基类自己的 run()。run() 是虚函数时,编译器不静态链接到该函数,而是链接到派生类的 run(),实现运行时多态。可以尝试去掉 virtual 再编译:输出会变成两次 Animal 的run()方法,多态失效。
虚函数表的机制(扩展):
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Qt开发指南》内容,为扩展知识。
编译器为含虚函数的类生成一张虚函数表(vtable),对象里保存一个指向该表的指针(vptr)。运行时通过 vptr 找到实际类型对应的函数地址,从而实现动态绑定:
flowchart LR
animal["Animal* animal"] -->|指向| dogObj["Dog 对象"]
dogObj -->|vptr| vtable["Dog 的 vtable\nrun() -> Dog::run"]
animal2["Animal* animal"] -->|指向| catObj["Cat 对象"]
catObj -->|vptr| vtable2["Cat 的 vtable\nrun() -> Cat::run"]
这也解释了为什么基类析构函数通常要声明为 virtual:通过基类指针 delete 派生类对象时,只有虚析构才能正确调用到派生类析构,避免资源泄漏。
封装是面向对象编程中把数据和操作数据的函数绑定在一起的概念,能避免外界干扰和误用,从而确保安全。数据封装引申出"数据隐藏"。
C++ 通过创建类来支持封装和数据隐藏,手段就是 public、protected、private。通常情况下把类成员状态设为 private,除非真的需要暴露,这样才能保证良好的封装性。
完整源码(10_encapsulation_example.cpp):addFood() 作为对外接口(public),total 作为私有成员(private),通过 getFood() 读取:
#include <iostream>
#include <string>
using namespace std;
class Dog
{
public:
string name;
Dog(int i = 0)
{
total = i;
}
void addFood(int number) {
total = total + number;
}
int getFood() {
return total;
}
private:
int total;
};
int main()
{
Dog dog;
dog.name = "旺财";
dog.addFood(3);
dog.addFood(2);
cout << dog.name << "总共获得了" << dog.getFood() << "份食物" << endl;
return 0;
}
逐行解释:
total。不初始化的话 total 是随机的 int 值,所以一般都在构造函数里初始化;不要在类内直接赋值初始化,有可能某些编译器不支持。addFood(int number) 把获得的食物份数累加到 total。getFood() 返回食物总份数,通过它访问私有成员 total。string name 是 public 的成员,可以直接 dog.name = "旺财"。数据抽象是指只向外界提供关键信息,隐藏后台实现细节,即只表现必要的信息而不呈现细节。它是一种依赖接口和实现分离的编程(设计)技术,好处:
例子:手机能拍照、听音乐、收音,用户直接使用即可,不需要知道拍照是如何通过摄像头取像、如何在屏幕上显示的。C++ 类为数据抽象提供了可能:向外界提供大量用于操作对象数据的公共方法,外界并不清楚类的内部实现。
cout 本身就是一个公共接口,我们不必知道它如何在屏幕上显示内容,底层已经实现好了。
数据封装与数据抽象的区别:封装是把数据和操作数据的函数捆绑在一起的机制;抽象是仅向用户暴露接口而隐藏实现细节的机制。带公有和私有成员的类都可以作为数据抽象的实例(例子参考 2.7)。
接口描述类的行为和功能,而不需要完成类的特定实现。C++ 接口是用抽象类实现的:
= 0 指定。完整源码(11_abstract_class.cpp):与多态例程类似,只是 Animal 的 run() 定义为纯虚函数,不用实现,由 Dog、Cat 重写:
#include <iostream>
using namespace std;
/* 定义一个动物类 */
class Animal
{
public:
virtual void run() = 0;
};
/* 定义一个狗类,并继承动物类 */
class Dog : public Animal
{
public:
void run() {
cout << "Dog 的run()方法" << endl;
}
};
/* 定义一个猫类,并继承动物类 */
class Cat : public Animal
{
public:
void run() {
cout << "Cat 的run()方法" << endl;
}
};
int main()
{
/* 实例化dog 对象 */
Dog dog;
/* 实例化cat 对象 */
Cat cat;
/* dog 调用run()方法 */
dog.run();
/* cat 调用run()方法 */
cat.run();
return 0;
}
结果与 2.6 的多态例程相同,但体现了两种不同的思想:多态例程是"基类给默认实现,派生类可选择重写",抽象类例程是"基类只给接口,派生类必须实现"。
多态与抽象类对比:
| 对比项 | 多态(虚函数有实现) | 接口(纯虚函数) |
|---|---|---|
| 基类函数 | virtual void run() {...} |
virtual void run() = 0; |
| 基类可实例化 | 可以 | 不可以(抽象类) |
| 派生类是否必须重写 | 否,不重写就用基类实现 | 是,否则派生类也无法实例化 |
| 典型用途 | 提供可覆盖的默认行为 | 定义统一接口/规范 |
Qt 中大量使用这两者:继承 QMainWindow 时可以重写 paintEvent() 等虚函数;而 QAbstractItemModel 这类抽象基类必须实现其纯虚接口才能使用。
所有例程都是单文件 C++ 程序(无 .h、无 .pro),在 Ubuntu 终端即可完成:
# 1. 创建目录并进入
mkdir -p C++/03_class_dog_example && cd C++/03_class_dog_example
# 2. 编辑源文件(vi/vim 或任意编辑器)
vi 03_class_dog_example.cpp
# 3. 编译
g++ 03_class_dog_example.cpp -o 03_class_dog_example
# 4. 运行
./03_class_dog_example
11 个例程(Embedded-Qt-Tutorial/C++/)与本文 2.x 各小节一一对应:01_hello_world(Hello World)、02_namespace_example(命名空间)、03_class_dog_example(类与对象)、04_structor_example(构造/析构)、05_this_pointer_example(this 指针)、06_inherit_example(继承)、07_func_overloading(函数重载)、08_operator_example(运算符重载)、09_polymorphism_example(多态)、10_encapsulation_example(数据封装)、11_abstract_class(抽象类/接口)。
| 现象 | 可能原因 | 排查手段 |
|---|---|---|
undefined reference to ... |
函数只声明未定义 | 检查是否写了函数体;构造函数/析构函数是否在类外漏写 Dog:: |
error: 'cout' was not declared |
缺少头文件或命名空间 | 加 #include <iostream> 与 using namespace std; |
error: 'string' was not declared |
漏包含 <string> |
加 #include <string> |
cannot be used as a function |
变量名与函数名冲突或少了分号 | 检查类定义末尾的 }; |
| 多态未生效 | 基类函数漏写 virtual |
给基类函数加 virtual |
| 内存/段错误 | 堆对象未 delete 或野指针 |
new 配 delete,释放后置 NULL |
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Qt开发指南》内容,为扩展知识。
答:构造函数与类名同名、可重载、无返回类型;在对象实例化时由系统自动调用,且仅调用一次,通常用来初始化成员。析构函数格式为 ~类名()、不能带参数、无返回值;在对象生命周期结束时自动调用,用于释放内存/资源。若用户未定义,编译器会自动生成一个空实现。栈对象离开作用域时析构,堆对象 delete 时析构。
this 指针是什么?能在静态函数里用吗?答:this 指向当前类的当前实例对象,记录对象内存地址,每个对象各有一个。成员函数通过它区分"操作的是哪个对象的数据"。this 只能在非静态成员函数中使用,全局函数和静态函数中都不能使用(静态函数不依赖具体对象)。它在成员函数开始时构造、结束时清除,存放位置由编译器决定(栈、寄存器或全局变量)。
答:两者都是同一作用域内为同名函数/运算符提供多个定义。函数重载要求形参的个数、类型或顺序不同,不能仅靠返回类型不同来区分。运算符重载使用 operator 加运算符符号的特殊函数形式,把运算符作用于自定义类型时转成函数调用;.、::、sizeof、?:、# 等不可重载。调用时编译器用实参类型与各定义形参比较,选出最合适的定义,这一过程叫重载决策。
答:多态指调用成员函数时根据对象的实际类型执行不同的函数。三个条件:存在继承关系;派生类与基类有同名虚函数(基类用 virtual 修饰);通过基类指针或引用调用虚函数。虚函数在基类中必须实现,派生类可选择重写;纯虚函数用 = 0 声明、没有实现,含有纯虚函数的类是抽象类,不能实例化,派生类必须实现所有纯虚函数才能实例化。本质上虚函数通过虚函数表实现动态(后期)绑定。
答:封装是把数据和操作数据的函数捆绑在一起,并用 public/protected/private 控制访问,通常数据成员设为 private、通过公共接口操作,从而保护内部状态;抽象是只暴露接口、隐藏实现细节,是一种接口与实现分离的设计技术。二者的实例都是"带公有和私有成员的类"。抽象类是实现接口的 C++ 手段:只要类中有至少一个纯虚函数它就是抽象类,不能实例化,只能作为基类/接口被继承,派生类实现其全部纯虚函数后才能实例化。
内容来源:《I.MX6U嵌入式Qt开发指南V1.2》第一章 在Ubuntu下编写C++、第二章 C++基础,以及正点原子 Qt 开发指南例程源码 Embedded-Qt-Tutorial/C++/01_hello_world ~ 11_abstract_class。