目录
初始化列表
问题描述:虽然之前调用构造函数后,对象中的成员变量已经有了初始值,但是这仍然不能称之为对对象中成员变量的初始化,只能叫做赋初值,因为成员变量只能被初始化一次,而之前的构造函数内可以进行多次“初始化”
基本概念:初始化列表是每个成员变量定义初始化的位置
格式:
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{
//函数体
}
private:
int _year; //缺省值
int _month;
int _day;
};
注意事项:
1、最好使用初始化列表初始化成员变量,因为即使不用默认构造函数也会先看看初始化列表中有没有东西然后再去看函数体中的内容
2、在初始化列表中自定义类型的成员变量在初始化时默认调用它们的默认构造函数(即使在原内容中并没有明确的写出要初始化该自定义类型的成员变量,如果没有默认构造函数就会报错)
#include <iostream>
using namespace std;
class B
{
public:
//全缺省的默认构造函数
B(int a = 1)//参数的个数最好和
:_a(a)
{
cout << "B(int a)" << endl;
}
private:
int _a;
};
class A
{
public:
A(int year, int month, int day, int& x)
: _year(year)
, _month(month)
, _day(day)
, _n(1)
, _ref(x)
, _aa(1)
{
}
private:
//声明
int _year;
int _month;
int _day;
//必须初始化
const int _n;
int& _ref;
B _aa;
};
int main()
{
int x = 10;
A d1(2024, 1, 31, x);
return 0;
}
调试过程: 20240310_233253-CSDN直播
如果没有默认构造就,可以直接显式调用(在初始化列表中给初始化参数):
A(int year, int month, int day, int& x)
: _year(year)
, _month(month)
, _day(day)
, _n(1)
, _ref(x)
, _aa(1)
{
}
如果有默认构造而且初始化列表中也有初始化,那么就按照初始化列表中给的参数来:
#include <iostream>
using namespace std;
class B
{
public:
B(int a = 1)
:_a(a)
{
cout << "B(int a)" << endl;
cout << a << endl;
}
private:
int _a;
};
class A
{
public:
A(int year, int month, int day, int& x)
: _year(year)
, _month(month)
, _day(day)
, _n(1)
, _ref(x)
, _aa(10)
{
}
private:
//声明
int _year;
int _month;
int _day;
//必须初始化
const int _n;
int& _ref;
B _aa;
};
int main()
{
int x = 10;
A d1(2024, 1, 31, x);
return 0;
}
3、 引用成员变量、const成员变量、自定义类型成员变量(且该类没有默认构造函数时)必须放在初始化列表中进行初始化
#include <iostream>
using namespace std;
class B
{
public:
B(int a, int b)//参数的个数应该和传入的参数个数相同(_aa(1,2)),即使不用
:_a(a)
,_b(b)
{
cout << "B(int a,int b)" << endl;
cout << a << endl;
cout << b << endl;
}
private:
int _a;
int _b;
};
class A
{
public:
A(int year, int month, int day, int& x)
: _year(year)
, _month(month)
, _day(day)
, _n(1)
, _ref(x)
, _aa(1, 2)
{
}
private:
//声明
int _year;
int _month;
int _day;
//必须初始化
const int _n;
int& _ref;
B _aa;
};
int main()
{
int x = 10;
A d1(2024, 1, 31, x);
return 0;
}
4、一个成员变量只能在初始化列表中出现一次
5、缺省值是给初始化列表用的(在初始化列表中没有给初始化值成员变量才会用缺省值)
缺省值可以有各种奇怪的形式:
privateL:
{
int _a = 1;
int* p = (int*)malloc(4);
A _aa = {1,2};
}
6、成员变量在类中的声明次序就是它在初始化列表中的初始化顺序
#include <iostream>
using namespace std;
class A
{
public:
A(int a)
:_a1(a)
,_a2(_a1)
{}
void Print()
{
cout << _a1 << "" << _a2 << endl;
}
private:
//int _a1;//下面有_a1和_a2交换位置的例子结果
int _a2;
int _a1;
};
int main()
{
A aa1(1);
aa1.Print();
return 0;
}
//A、输出1-1
//B、程序崩溃
//C、编译不通过
//D、输出1-随机值
在进入初始化列表时会先看先声明的_a2而非后声明的_a1,在检查_a2的时候_a1的值还是随机值,所以_a2的值就会被初始化为随机值,然后再检查_a1的时候用传入的1初始化即可,如果先对_a1声明,那么在检查到_a2的时候发现_a1已经被声明为1了_a2也就会被声明为1
本质原因:C++中类的成员变量在内存中的布局确实是按照它们在类定义中声明的顺序进行排列的
结论:声明顺序和初始化列表初始化的顺序最好一致
7、初始化列表与结构体要(可以)配合使用
#include <iostream>
using namespace std;
class B
{
public:
B(int a, int b)//参数的个数最好和
:_a(a)
,_b(b)
{
cout << "B(int a,int b)" << endl;
cout << a << endl;
cout << b << endl;
}
private:
int _a;
int _b;
};
class A
{
public:
A(int year, int month, int day, int& x)
: _year(year)
, _month(month)
, _day(day)
, _n(1)
, _ref(x)
, _aa(1, 2)
,_p((int*)malloc(sizeof(4)))
{
if (_p == nullptr)
{
perror("malloc fail");
}
}
private:
//声明
int _year;
int _month;
int _day;
//必须初始化
const int _n;
int& _ref;
B _aa;
int* _p;
};
int main()
{
int x = 10;
A d1(2024, 1, 31, x);
return 0;
}
8、 先进行初始化队列中的初始化后进行函数体中内容(初始化或者其它内容)
9、单参数构造函数支持隐式类型的转换
#include <iostream>
using namespace std;
class C
{
public:
C(int x = 0)
:_x(x)
{}
void Print()
{
cout << _x << endl;
}
private:
int _x;
};
int main()
{
C c1(1);
C c2 = 2;
c1.Print();
c2.Print();
return 0;
}
在使用2时会用默认构造函数生成一个临时对象,将临时对象拷贝构造给对象cc2(跟赋值时类似)
但是实际上并没有调用拷贝构造
#include <iostream>
using namespace std;
class C
{
public:
C(int x = 0)
:_x(x)
{}
C(const C& cc)
{
cout << "const C& cc" << endl;
}
private:
int _x;
};
int main()
{
C c1(1);
C c2 = 2;
return 0;
}
这是因为编译器优化的原因:同一表达式连续步骤的构造,一般会被合二为一(直接构造)
Const C& cc3 = 2;//为什么可以?
int i = 1;
const double& d = i;
因为cc3引用的不是2而是临时变量,临时变量具有常性,所以加上了const修饰,故不会报错,对于下面的double也一样,d引用的不是i而是源自于i的临时变量,临时变量有常性所以加const修饰
有了这一隐式转换的规则“单参数构造函数支持隐式类型的转换”可以实现我们为栈类插入一个自定义类型的值时可以直接用后者代替前者(整型可以传递给自定义类型):
定义一个栈类的对象st后向栈中插入自定义类型的数据:
- 前者:定义一个C类的cc3对象并用3进行初始化,然后再向栈中的Push函数传入已经初始化好的cc3(Push中的c就是cc3的别名)
- 后者:直接向Push函数中传入一个整型4,由于隐式转换4在传入前会构造一个被4初始化好的cc3类型的临时变量然后将该临时变量传递给c,c就是该临时变量的别名了
前者和后者实现步骤看起来是一样的,该隐式转换的规则表明我们既可以向Push中传递整型也可以传递自定义类型的对象
10、C++98仅支持单参数的隐式类型转换,但是C++11支持多参数的隐式转换(二者原理一样)
#include <iostream>
using namespace std;
class C
{
public:
C(int a1, int a2)
:_a1(a1)
,_a2(a2)
{}
private:
int _a1;
int _a2;
};
int main()
{
C aa1 = { 1,2 };
const C& aa2 = { 2,3 };
C aa3(5, 6);
return 0;
}
explict关键字
作用:不发生“单/双参数构造函数支持隐式类型的转换”的隐式传唤
#include <iostream>
using namespace std;
class C
{
public:
explicit C(int x = 0)
:_x(x)
{}
void Print()
{
cout << _x << endl;
}
private:
int _x;
};
int main()
{
C c1(1);
C c2 = 2;
return 0;
}
static成员
基本概念:static修饰的类成员称为类的静态成员,static修饰的成员变量称为静态成员变量,static修饰的成员函数称为静态成员函数
注意事项:
1、静态成员变量一定要在类外进行初始化,定义时不加static关键字,类中声明时加
2、静态成员变量或静态成员函数属于同一类中的所有对象
3、静态成员函数没有this指针
4、静态成员函数不可以调用非静态成员函数
5、非静态成员函数可以调用静态成员函数
6、静态成员变量可以用类名::静态成员或者对象.静态成员来访问
7、静态成员也是类的成员,受public、protected、private访问限定符的限制
8、静态成员函数不能使用任何非静态成员
面试题:计算程序中创建了多少个类对象
#include <iostream>
using namespace std;
class A
{
public:
A()
{
++_count;//如果不是拷贝构造出的对象就会调用默认构造,此时++count
};
A(const A& a)
{
++_count; //如果是拷贝构造出的对象就会调用拷贝构造函数,此时++count
}
~A()
{
--_count; //当一个对象销毁是--count
}
static int Total()
{
return _count; //返回_count的值
}
private:
static int _count;
};
int A::_count = 0;
int main()
{
cout << A::Total() << endl;
A a1, a2;
A a3(a1);
cout << A::Total() << endl;
return 0;
}
~over~