tips
- 在引用与指针传参的时候,都涉及到权限的放大缩小问题,都需要特别去注意一下。关于引用的权限放大缩小以及判断,在我之前的博客里面就有写过;对于指针的权限放大缩小问题,就看星号前面是否修饰了const。他们两个的权限放大缩小,问题都在于:当进行引用与指针传参的时候,权限只能够平移或缩小,而不能够进行放大
- 只要但凡是个自定义类型,运用运算符的时候,需要对运算符进行重载,因为自定义类型是我们自己定义的,电脑里面的运算符它只知道内置类型怎么去处理,对于我们自己定义的类型他是不知道的,需要我们自己去重载实现
- 默认成员函数的运算符重载都不能给他重载成全局函数,因为如果你在类当中没有实现的话,编译器会自己生成一个,这样子的话就会与你全局定义的那个运算符重载会发生冲突
初始化列表的引入与介绍
- 当你去创建一个实例化对象的时候,与此同时也就相当于意味着去创建了这个实例化对象当中的各种各样的成员变量,这些成员变量有内置类型、自定义类型的,***那些自定义类型的成员变量也相当于是某个类的实例化对象,可以把它理解成麻雀虽小,五脏俱全。***那么对于那个自定义类型的成员变量一旦创建,根据一语双关的原则,也就必须得去调用属于该类的一个构造函数。根据语法格式来看的话,那些自定义类型成员变量它所调用的构造函数是一个无参的默认构造函数。不管是编译器自己生成的,还是你自己写的一个构造函数,在没有引入初始化列表这个概念之前,都是没有办法去调用自定义类型变量麾下的有参的构造函数。
- 我们之前知道在创建对象的时候,编译器会调用构造函数,给对象中每一个成员变量一个合适的初始值,虽然在上述构造函数调用之后,对象当中已经有了一个初始值,但是这个绝对不能够称为对象中成员变量的初始化。首先必须得知道初始化与赋初值的区别,初始化是只能够初始化一次,你见过哪里有初始化多次的?在这个构造函数体内,你可以多次进行赋值,那你哪里还有资格称你自己是初始化?所以在构造函数体内被称为赋初值,就是给个初始值,这个是正确的,但绝对不能够投换概念成初始化。在没有引入成员列表之前,之前的那些构造函数的操作称为类实例化对象的成员变量当中去给一个初始值,但这个初始值并不能够称之为初始化,因为初始化的话它的次数只能够是一次,然后你这边的话,它可以进行多次的一个赋值。
- 有时候这个类的实例化对象当中就有那么一些极其特殊的成员变量(比方说引用与const修饰的常量),必须在定义的时候就得初始化,非初始化不可。这时候,之前的这些构造函数就不能够满足要求,之前的那些构造函数体内被称为赋初值而不是初始化。然后就需要引入初始化列表这个概念,初始化列表他也是在构造函数里面,他就是专门用来为那些成员变量进行初始化的一个地方。
- 引入了初始化列表这个概念之后,与此同时就也可以去调用自定义类型成员变量的非默认构造函数了。
- 上面是引入,接下来介绍:初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括
号中的初始值或表达式。
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
初始化列表与构造函数的关系
- 初始化列表的话是构造函数的一部分,不是说要什么取代构造函数之类的
- 有了初始化列表的话,有绝大部分成员变量的赋初值这些的话原先都是在构造函数体内完成,现在的话都可以给他放到初始化列表那边去进行一个初始化,效果都是一样的。
- 但是初始化列表的新的加入并不能够完全取代函数体内赋值,总有那么一些工作并不适合在初始化列表当中去进行初始化,还是会需要用到函数体内复制,就是说构造函数的函数体内有时候还是有作用的,举几个简单的例子,比方说:malloc开辟空间后指针的检查,以及二维数组的动态开辟等等之类的
- 如下是函数体内赋初值
class Stack
{
public:
Stack(int size = 0, int capacity = 10)
{
_size = size;
_capacity = capacity;
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == nullptr)
{
perror("malloc failed");
return;
}
}
private:
int* _a;
int _size;
int _capacity;
};
- 如下是构造函数里面用初始化列表
class Stack
{
public:
Stack(int size = 0, int capacity = 10)
:_a((int*)malloc(sizeof(int)* capacity))
,_size(size)
, _capacity(capacity)
{
if (_a == nullptr)
{
perror("malloc failed");
return;
}
}
private:
int* _a;
int _size;
int _capacity;
};
- 如下:将初始化列表与函数体赋值两者结合到构造函数里面去实现二维数组的动态开辟
class array2
{
public:
array2(int row = 10, int col = 10)
: _row(row)
, _col(col)
{
_a = (int**)malloc(sizeof(int*) * _row);
if (_a == nullptr)
{
perror("malloc failed");
return;
}
for (int i = 0; i < _row; i++)
{
_a[i] = (int*)malloc(sizeof(int) * _col);
if (_a[i] == nullptr)
{
perror("malloc failed");
return;
}
}
}
void input()
{
for (int i = 0; i < _row; i++)
{
for (int j = 0; j < _col; j++)
{
*(_a[i] + j) = j;
}
}
}
void output()
{
for (int i = 0; i < _row; i++)
{
for (int j = 0; j < _col; j++)
{
cout << *(_a[i] + j) << " ";
}
cout << endl;
}
}
~array2()
{
for (int i = 0; i < _row; i++)
{
free(_a[i]);
}
free(_a);
}
private:
int** _a;
int _row;
int _col;
};
int main()
{
array2 a(10,10);
a.input();
a.output();
return 0;
}
初始化列表的注意点
- 初始化列表这边有个极其巨大的坑:成员变量在类当中声明的次序就决定了在初始化列表当中的初始化顺序,这个与在初始化列表当中的语句前后次序无关,但是在构造函数体内的话,就没有这个问题,还是按你写的函数语句依次执行,如下面这个代码执行结果为1, 随机值
class A
{
public:
A(int a)
:_a1(a)
, _a2(_a1)
{}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2;
int _a1;
};
int main()
{
A aa(1);
aa.Print();
}
- 因此在初始化列表当中,在类当中,成员变量声明的顺序与在初始化列表当中定义的顺序要保持一致
- 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
初始化列表的总结