小学生学C++编程语法知识(C++类初始化详解(一))
C++类初始化详解(一):
很多同学第一次学习类(class)时,会觉得:
“我创建一个对象,不就是给变量赋值吗?为什么还要学习构造函数、初始化列表?”
其实,类初始化是C++面向对象最重要的基础之一。
今天我们解决几个核心问题:
一个类的成员变量有哪些初始化方式?
构造函数到底什么时候执行?
成员变量初始化顺序是什么?
为什么成员初始化列表更快?
哪些情况必须使用初始化列表?
一、先理解:什么叫初始化?
假设我们设计一个学生类:
class Student { public: string name; int age; };创建对象:
Student s;现在:
s +-------------+ | name | | age | +-------------+但是:
name是什么? age是多少?不知道。
所以我们需要给成员变量一个初始值。
这个过程叫:
初始化(Initialization)
二、类成员初始化的三种方式
C++中常见有三种方式。
方式1:创建对象后赋值
最简单:
class Student { public: string name; int age; }; int main() { Student s; s.name="Tom"; s.age=10; }过程:
第一步:
创建对象:
Student s;产生:
s +-------+ |name ? | |age ? | +-------+第二步:
赋值:
s.name="Tom"; s.age=10;变成:
+---------+ |Tom | |10 | +---------+这种方法的问题:
对象已经出生了,然后再修改。
类似:
小孩出生:
宝宝出生 ↓ 再告诉他名字 ↓ 再告诉他年龄效率低。
方式2:构造函数内部赋值
例如:
class Student { public: string name; int age; Student() { name="Tom"; age=10; } };使用:
Student s;执行:
进入构造函数 name="Tom" age=10结果:
Student对象 name Tom age 10看起来很好。
但是这里隐藏了一个问题。
三、重点:成员变量其实已经初始化了一次
例如:
class Student { public: string name; Student() { name="Tom"; } };很多学生认为:
执行:
Student s;过程:
name产生 ↓ name="Tom"实际上不是。
真实过程:
第一步: string name创建 第二步: name调用默认构造函数 第三步: name="Tom"也就是说:
发生了:
初始化一次 + 赋值一次例如:
string:
第一次:
name=""然后:
name="Tom"实际上:
空字符串 ↓ 赋值Tom多了一步。
四、方式3:成员初始化列表(推荐)
写法:
class Student { public: string name; int age; Student() : name("Tom"), age(10) { } };注意:
冒号:
:后面叫:
成员初始化列表
执行过程:
直接:
创建name 同时初始化为Tom创建:
name="Tom"一步完成。
所以:
构造函数赋值:
创建 ↓ 默认初始化 ↓ 赋值初始化列表:
创建 ↓ 直接初始化少了一步。
五、用生活例子理解
假设你买手机。
方法1:
买一个空手机:
手机出厂 ↓ 打开 ↓ 安装系统 ↓ 设置名字方法2:
厂家直接:
生产手机时 直接装好系统 写好名字当然第二种更快。
六、构造函数执行顺序(非常重要)
很多考试喜欢考。
例如:
class A { public: int x; int y; A() { cout<<"构造函数"<<endl; } };创建:
A a;执行:
1. 分配空间 2. 初始化成员变量 3. 执行构造函数 4. 对象创建完成重点:
成员变量初始化发生在构造函数代码执行之前。
七、成员初始化顺序是谁决定的?
很多学生认为:
A() :x(10),y(20) { }按照:
x 然后y执行。
对吗?
不一定!
真正规则:
按成员变量在类中声明的顺序初始化。
例如:
class A { public: int x; int y; A() :y(20),x(10) { } };很多人认为:
y先初始化 x后初始化错误!
实际:
x先初始化 y后初始化因为:
类里面:
int x; int y;顺序决定。
记忆:
初始化列表顺序,看列表;
真正执行顺序,看成员声明。
八、为什么初始化列表更快?
我们比较:
方法1
class Student { public: string name; Student() { name="Tom"; } };过程:
创建name ↓ 调用string默认构造 ↓ name="Tom"两步。
方法2
Student() :name("Tom") { }过程:
直接调用string有参构造 创建Tom一步。
所以:
初始化列表:
减少:
临时对象 额外赋值 资源复制效率更高。
九、哪些成员必须使用初始化列表?
这是重点。
1. const成员变量
例如:
class Student { public: const int age; Student() { age=10; } };错误!
为什么?
因为:
const变量:
出生以后不能修改但是:
age=10属于修改。
必须:
Student() :age(10) { }2. 引用成员变量
例如:
class Test { int &x; };引用必须绑定对象。
不能:
x=10;必须:
Test(int a) :x(a) { }3. 没有默认构造函数的对象成员
例如:
class Phone { public: Phone(int price) { } };没有:
Phone()那么:
下面错误:
class Person { Phone phone; Person() { } };因为:
创建Person时:
必须先创建phone。
但是:
phone不知道怎么创建。
解决:
Person() :phone(1000) { }十、多个成员变量初始化
例如:
class Student { string name; int age; double score; public: Student(string n,int a,double s) :name(n),age(a),score(s) { } };创建:
Student s("Tom",10,99.5);过程:
name="Tom" age=10 score=99.5十一、父类和成员对象初始化顺序(进阶)
学习继承会遇到。
对象出生:
顺序:
1. 父类初始化 2. 成员对象初始化 3. 自己成员初始化 4. 构造函数主体例如:
class Child:public Parent { };创建Child:
Parent出生 ↓ Child成员出生 ↓ Child构造函数十二、析构函数的顺序(对应)
出生:
父类 ↓ 成员 ↓ 自己死亡:
反过来:
自己析构 ↓ 成员析构 ↓ 父类析构十三、小学生记忆口诀
构造函数:
对象出生自动调用 先成员 后自己初始化列表:
冒号后面写初始化 比函数里面赋值快 const引用必须用它成员初始化顺序:
不要看列表排哪里 只看类里声明序十四、竞赛常见考点总结
| 知识点 | 是否常考 |
|---|---|
| 构造函数自动执行 | ⭐⭐⭐⭐ |
| 初始化列表语法 | ⭐⭐⭐⭐ |
| const成员必须初始化列表 | ⭐⭐⭐⭐⭐ |
| 引用成员必须初始化列表 | ⭐⭐⭐⭐⭐ |
| 成员初始化顺序 | ⭐⭐⭐⭐⭐ |
| 初始化列表效率原因 | ⭐⭐⭐ |
给学生的一句话总结:
构造函数负责“对象出生时做什么”,成员初始化列表负责“对象出生时是什么样”。
真正高级的C++程序员,会尽量让对象一出生就是完整状态,而不是出生以后再修改它。
