类和对象--中--初始化列表(重要)、隐式类型转化(理解)、最后两个默认成员函数
1.初始化列表
1.1作用:
通过特定的值,来初始化对象。
1.2定义:
初始化列表,就相当于定义对象(开空间)。不管写不写初始化列表,每个成员变量都会走一遍初始化列表(开出对应的空间)。这就是为什么无参构造函数,直接在函数体写赋值语句就好了,因为已经开好空间了。
注:初始化列表和拷贝构造有相同点,都需要传参。但是拷贝构造肯定要用这个参数来初始化对象,初始化列表可以不用这个参数,有这个参数,只是为了和默认构造区分开。
1.3格式:
初始化列表的使⽤⽅式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成 员列表,每个"成员变量"后⾯跟⼀个放在括号中的初始值或表达式。初始化列表需要传参。
注:
区分初始化和赋值。
1.4特性:
1.每个成员变量在初始化列表中只能出现⼀次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地⽅。
2.引⽤成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进⾏初始化,否则会编译报错。
3.C++11⽀持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显⽰在初始化列表初始化的成员使⽤的。
4.初始化列表中按照成员变量在类中声明顺序进⾏初始化,跟成员在初始化列表出现的的先后顺序⽆关。建议声明顺序和初始化列表顺序保持⼀致。
1.5案例:
案例一: 体现 特性1
案例一解析:
为什么只能出现一次?可以理解为,初始化列表就是定义变量的地方,类似于发生重定义。
(至于为什么这么理解,本人现在无法解释,先记忆住这个知识点吧。)
案例二: 拷贝构造和初始化列表的关系
既然初始化列表可以传参,那也可以将 类 传参。那么拷贝构造和初始化列表写法就一样了。哪个是拷贝?哪个是初始化列表?
构造:
初始化列表:
案例二解析:
初始化列表和拷贝构造,最根本的区别就是:初始化列表用不用这个参数,无所谓,拷贝构造要用这个参数来初始化对象。既然初始化列表可以不用这个参数,那么这个参数的价值是什么?为了和默认构造函数构成函数重载
代码参考:
class A {
public://无参构造A(){_a = 10;_b = 20;}//A()// //:_a(100)// //,_b(200)//{//_a = 15;//_b = 30;//_c = 50;//}A(const A& aa) {_a = aa._a;}//A(const A& bb) // //:_a(100)// //,_b(200)//{// _a = 15;// _b = 30;// _c = 50;//}private:int _a=0;int _b=0;int _c=0;
};
int main() {A a1;//哪个是拷贝?哪个是初始化列表?A a2(a1);A a3;getchar();return 0;
}
案例三: 特性2
案例三第一部分: 特性2
const修饰的变量和引用一样,这两者必须在初始化的时候就赋值。不能通过赋值语句来赋值。
为什么会报错?这就需要提及1.3的注了,这里函数体是赋值的地方。那怎样写才是对的?
案例三第一部分解析:
const 和 引用这是两个特例,需要格外记忆。
案例三第二部分: 特性2
案例三第二部分解析:
通过案例二,我们知道,初始化列表需要传参,是为了和构造函数,构成函数重载。而且初始化列表和构造的区别就是,初始化列表可以不用这个参数。
通过案例三第二部分,我们知道。如果 类(MuQueue) 成员有自定义类型(Stack),且该自定义类型没有默认构造,那么初始化 类 ,就会导致编译的时候就会报错。如果用初始化列表来初始化 类 ,就可以指定该自定义类型如何初始化。
代码参考:
class Stack {
public:Stack(int n) {_a = (int*)malloc(sizeof(int) * n);_top = 0;_capacity = n;}
private:int* _a;int _top = 0;int _capacity = 0;
};class MyQueue {
public:/*MyQueue() {cout << "MyQueue()" << endl;_size = 10010;}*/MyQueue(int n):pushst(n), popst(n), _size(0){cout << "MyQueue(int n) " << endl;}
private:Stack pushst;Stack popst;int _size;
};int main() {//调用的是构造函数//MyQueue q1;//调用的是初始化列表MyQueue q2(20);return 0;
}
案例四: 特性4
案例四解析:
在VS2019及之后的版本中。编译器优化掉这一部分了。这一特性仅供了解。
1.6思考:为什么要用初始化列表?初始化列表有什么用?我用构造函数,不能满足要求么?
答: 因为那些你不在初始化列表初始化的成员也会⾛初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会⽤这个缺省值初始化。
如果你没有给缺省值,对于没有显⽰在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有显⽰在初始化列表初始化的⾃定义类型成员会调⽤这个成员类型的默认构造函数,如果没有默认构造会编译错误。
1.6总结:
- ⽆论是否显⽰写初始化列表,每个构造函数都有初始化列表;
- ⽆论是否在初始化列表显⽰初始化,每个成员变量都要⾛初始化列表初始化;
2.隐式类型转化
2.1作用:
提高了代码的效率,同时提高了代码的可读性。
2.2案例
案例一: 内置类型的隐式类型转化
案例一解析:
如果两个不同类型的对象要赋值。那么就会生成一个临时量来存值。
案例二: 自定义类型的隐式类型转化
案例二解析:
第一个 a1 是什么,好理解,就是用的初始化列表。可是 a2 为什么没有报错啊?一个是 int 类型,一个是 A 类型。
这里就和 案例一 类似了。
而VS2019之后的版本,对于 连续的构造、拷贝构造,直接优化为构造了。没有中间的拷贝构造了。
当然,这里不会报错的本质是因为:A 类型是单参数(且是int 类型)的构造。
案例三: 隐式类型转化的好处
案例三解析:
提高了代码的可读性。
代码参考:案例五
案例四: 取消隐式类型转化
代码参考:案例五
案例五: 多参数的隐式构造
案例五解析:
多参数的,需要用 ' {} ' 。
代码参考:
class A {
public:A(int a, int b):_a1(a), _a2(b) {}
private:int _a1;int _a2;
};
class Stack {
public:void Push(const A& aa) {//...}
};
int main() {A test = { 100,200 };Stack st;st.Push(test);st.Push({100,200});return 0;
}
3.最后两个默认成员函数
3.1const来修饰成员函数(理解)
作用:
为了让传参的时候,发生权限的放大,导致调用函数失败。
格式:
在函数声明和定义的后面,加长const
案例一:
d1的权限是只读,但是print的参数权限是可读可写。
这里传参发生了,对参数权限的放大,所以会报错。如果用const修饰成员函数,那么就不会发生参数权限的放大。
案例二:
这里很奇怪,只需要交换一下参数的顺序,就不会报错了。和案例一类似,在传参的时候权限发生了放大。
总结:
如果在给成员函数传参的时候,不想让参数在函数内得到改变,就将成员函数用const修饰(本质就是给成员函数的所有参数,加一个const)
3.2取地址操作符重载(了解)
取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,⼀般这两个函数编译器⾃动⽣成的就可以够我们⽤了,不需要去显⽰实现。除⾮⼀些很特殊的场景,⽐如我们不想让别⼈取到当前类对象的地址,就可以⾃⼰实现⼀份,胡乱返回⼀个地址。
都是获取一个地址,上面两张图,并没有什么明显的变化。
下面这张图就不是了:返回的是一个地址,至于能不能用,就不知道了。所以说,实践意义并不大。