目录
初始化列表
构造函数体赋值
构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
初始化列表格式
以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。下面以栈为例:
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 4)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
// 其他方法...
~Stack()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
我们实现用两个栈实现队列该怎么写类呢?
class myqueue
{
public:
// 初始化列表:本质可以理解为每个对象中成员定义的地方
myqueue(int n, int& rr)
// stack不具备默认构造,myqueue也无法生成默认构造
:_pushst(n)
,_popst(n)
{
_size = 0;
}
private:
// 声明
Stack _pushst;
Stack _popst;
int _size;
};
初始化列表特性
每个成员变量在初始化列表中只能出现一次
每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
类中以下成员必须初始化
所有的成员,可以在初始化列表初始化,也可以在函数体内初始化(即构造函数),以下三个必须初始化:
1、引用 2、const 3、没有默认构造自定义类型成员(必须显示传参调构造)
class myq
{
public:
myq(int n, int& rr)
,_x(1)
,_ref(rr) //_ref是rr的别名
{
//_x = 1;
}
private:
// 必须在定义时初始化
const int _x = 2;
// 必须在定义时初始化
int& _ref;
};
_x在类成员变量里给了缺省值 2 ,当我们初始化_x时,初始化列表给_x初始化的值优先于类成员变量给的补丁初始化,如果初始化列表没有给值初始化,就调用补丁里的值。
尽量使用初始化列表初始化
记住以下几点:
- 初始化列表,不管你写不写,每个成员变量都会先走一遍。
- 自定义类型的成员会调用默认构造(没有默认构造就编译报错)
- 内置类型有缺省值用缺省值,没有的话,不确定,要看编译器,有的编译器会处理,有的不会处理。
- 先走初始化列表 , 再走函数体。
- 实践中:尽可能使用初始化列表初始化,不方便再使用函数体初始化。
数组初始化
class arrayy
{
public:
arrayy() //开辟一个存放十个int的空间
: _arr((int*)malloc(sizeof(int) * 10))
{
memset(_arr, 0, sizeof(int)*10); //10个元素置0
}
private:
int* _arr;
};
int main()
{
arrayy a;
return 0;
}
声明次序就是初始化顺序
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后 次序无关。如以下代码所示:
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();
return 0;
}
//输出1 1 还是输出1 随机值 ?
答案是输出1 随机值,原因:在声明时(private)_a2在_a1前面,初始化列表先给_a2初始化,_a1赋值给_a2,但是_a1是随机值,_a2接收了一个随机值,然后又用1给_a1进行初始化。
多参数初始化列表
class A
{
public:
//单参数构造函数
A(int a)
:_a(a)
{
cout << "A(int a)" << endl;
}
//多参数构造函数
A(int a1, int a2)
:_a(0)
,_a1(a1)
,_a2(a2)
{}
private:
int _a;
int _a1;
int _a2;
};
int main()
{
//A aaa1(1, 2); //此处能通过是因为这里是逗号表达式,返回2调用了单参数构造函数
A aaa2 = { 1, 2 };
const A& aaa3 = { 1, 2 }; //产生隐式类型转换
//上面aaa2,aaa3的等号可以省略,但不建议
return 0;
}
注意多参数初始化列表调用时使用的是{ }
再谈隐式类型转换
拷贝
先看以下代码:
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
int main()
{
A aa1(1);
// 拷贝构造
A aa2 = aa1;
// 隐式类型转换 (内置类型转换为自定义类型)
A aa3 = 3;
return 0;
}
aa1拷贝构造给aa2可以理解,为什么3(int类型)可以给aa3(自定义类型)拷贝构造呢?
由上图可知,编译器会先用3构造一个A类型的临时变量,然后用这个临时变量拷贝构造给aa3,但是深入发现:编译器遇到连续构造+拷贝构造->优化为直接构造,如aa3。
引用
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
int main()
{
A& ra = 3; //报错
const A& ra = 3; //通过
return 0;
}
为什么第一句无法实现,而加上const就可以了呢?下图解释:
这里和上面的拷贝构造一样,优化为直接拷贝,注意加上const。
explicit关键字
定义
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值 的构造函数,还具有类型转换的作用。
即用explicit修饰构造函数,将会禁止构造函数的隐式转换。
用法
class A
{
public:
explicit A(int a)
:_a(a)
{}
private:
int _a;
};
int main()
{
const A& ra = 3; //报错
return 0;
}
这时候3不能构造给临时对象,无法进行隐式类型转换。
缺省值的多种方式
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 4)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
// 其他方法...
~Stack()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
class A
{
public:
//单参数构造函数
A(int a)
:_a(a)
{
cout << "A(int a)" << endl;
}
//多参数构造函数
A(int a1, int a2)
:_a(0) //可以给变量,也可以给常量
,_a1(a1) //一般建议给常量
,_a2(a2)
{}
private:
int _a;
int _a1;
int _a2;
};
class BB
{
public:
BB()
{
}
private:
// 声明给缺省值
int _b1 = 1;
int* _ptr = (int*)malloc(40); //开辟一个40字节的空间
Stack _pushst = 10; //capacity初始化为10
A _a1 = 1; //单参数构造函数
A _a2 = { 1,2 }; //多参数构造函数
A _a3 = _a2;
};
int main()
{
BB bb;
return 0;
}
方式在成员变量那里展示!
static成员
概念
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用 static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化
class A
{
public:
A() //构造函数
{
++_scount;
}
A(const A & t) //拷贝构造函数
{
//GetCount();
++_scount;
}
~A()//析构函数
{
//--_scount;
}
static int _scount;
private:
// 声明
int _a1 = 1;
int _a2 = 1;
};
// 定义
int A::_scount = 0;
为什么_scount在哪里都可以呢?原因是他存放在静态区,不存在对象中,并且不能给缺省值,因为缺省值是给初始化列表,他在静态区不在对象中,不走初始化列表。他属于所有整个类,属于所有对象。
特性
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问(类静态成员必须在public中)
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
int main()
{
A aa1;
cout << sizeof(aa1) << endl; //输出8
//因为_scount在静态区,不占aa1的空间
//aa1._scount++;
//cout << A::_scount << endl;
//以上语句只能静态成员在public中才能实现
return 0;
}
那静态成员变量受private限制应该怎么访问呢?以下代码可以实现:
class A
{
public:
A() //构造函数
{
++_scount;
}
A(const A & t) //拷贝构造函数
{
//GetCount();
++_scount;
}
~A()//析构函数
{
//--_scount;
}
// 没有this指针,只能访问静态成员
static int GetCount()
{
//_a1 = 1; //无法访问
return _scount;
}
private:
// 声明
int _a1 = 1;
int _a2 = 1;
static int _scount;
};
// 定义
int A::_scount = 0;
这里的静态成员函数没有this指针,只能访问静态成员变量,我们要静态成员变量有什么用呢?
A func()
{
A aa4; //构造 //_scount = 4
//返回值调用了一次拷贝构造,原因是传值调用
//vs2019会+1,vs2022不会+1
return aa4;
}
int main()
{
A aa1; //构造 //_scount = 1
A aa2; //构造 //_scount = 2
A aa3(aa1); //拷贝构造 //_scount = 3
func(); //_scount = 4
cout << A::GetCount() << endl; //输出4
return 0;
}
联系上A类里的成员函数和成员变量,我们通过静态成员变量记录拷贝构造和构造的次数,main函数里共构造了4个变量,所以输出为4。