个人主页 : zxctscl
文章封面来自:艺术家–贤海林
如有转载请先通知
文章目录
- 1. 前言
- 2. 再谈构造函数
- 2.1 构造函数体赋值
- 2.2 初始化列表
- 2.3 explicit关键字
- 3. static成员
- 3.1 概念
- 3.2 特性
1. 前言
在前面的博客中已经分享有关构造函数 【C++】构造函数和析构函数详解,这次又再一次提到构造函数,一起来看看。
2. 再谈构造函数
2.1 构造函数体赋值
在之谈到构造函数时候是在函数体里面初始化,举个例子:
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
2.2 初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个**"成员变量"后面跟一个放在括号中的初始值或表达式**。
#include<iostream>
using namespace std;
class Date
{
public:
// 初始化列表
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
其他的成员既可以在初始化列表,也可以在函数体。
这里_n没有办法初始化,它只能在函数体。
声明并没有定义,是在对象实例化的时候才整体定义。
但是有一些成员必须在定义的时候初始化。
如果函数体里面出现像下面_year这样的情况,该怎么办?
所以c++中用了初始化列表,初始化列表是每个成员变量定义初始化的位置。
下面的成员变量也会走初始化列表,他们也要定义,只是没有给值就是随机值,如果给了值就直接初始化。
在既有缺省值(不给值就用缺省值)又有初始化列表,走的是初始化列表的值。
先走初始化列表再走下面的赋值修改。
那么函数体和初始化列表哪个好用呢?
初始化列表是每个成员变量定义初始化的位置, 能用初始化列表就建议用初始化列表。
不用也会先走初始化列表。
哪些成员必须用初始化列表呢?
const,引用(引用必须在定义的时候初始化)
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a = 0)" << endl;
}
private:
int _a;
};
class Date
{
public:
Date(int year, int month, int day, int& x)
:_year(year)
, _month(month)
, _day(day)
, _n(1)
, _ref(x)
{
}
private:
// 声明
int _year;
int _month;
int _day;
const int _n;
int& _ref;
A _aa;
};
int main()
{
int x = 10;
Date d1(2024, 1, 31, x);
return 0;
}
这里会调A的默认构造函数,不传参也会调。
如果A没有默认构造调怎么办?
那就用初始化列表。
这里是显示的调构造
【注意】
-
每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
-
类中包含以下成员,必须放在初始化列表位置进行初始化,不能在函数体内初始化:
(1)引用成员变量
(2)const成员变量
(3)自定义类型成员(且该类没有默认构造函数时) -
尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
当然混合使用也是可以的。
class A
{
public:
A(int a = 0,int b=1)
:_a(a)
{
cout << "A(int a = 0)" << endl;
}
private:
int _a;
};
class Date
{
public:
Date(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) * 10))
{
if (_p == nullptr)
{
perror("malloc fail");
}
}
private:
// 声明
int _year;
int _month;
int _day;
const int _n;
int& _ref;
A _aa;
int* _p;
};
int main()
{
// 对象实例化
int x = 10;
Date d1(2024, 1, 31, x);
A aa(2, 3);
return 0;
}
class B
{
private:
// 缺省值
int a = 1;
int* p1 = nullptr;
int* p2 = (int*)malloc(4);
}
int main()
{
B bb;
return 0;
}
初始化列表能像下面这样写,缺省值也能这样写。
class C
{
public:
//explicit C(int x = 0)
C(int x = 0)
:_x(x)
{}
private:
int _x;
};
class B
{
private:
// 缺省值
int a = 1;
int* p1 = nullptr;
int* p2 = (int*)malloc(4);
}
int main()
{
B bb;
C cc1(1);
C cc2 = 2;
return 0;
}
这样也可以,是为什么呢?
这里是:单参数构造函数支持隐式类型的转换。
2构造一个临时对象,再拷贝构造
像下面那种原理一样:
---------------------------------------------------------------------------------------------------------------------------------
如果有拷贝构造会不会调用呢?
不会,2构造一个临时对象,再拷贝构造 -> 编译器优化了,同一个表达式连续步骤的构造,一般会被合二为一
---------------------------------------------------------------------------------------------------------------------------------
这个代码为什么可以?
类型转换会产生临时变量。
就像下面这样,临时变量具有常性。
---------------------------------------------------------------------------------------------------------------------------------
内置类型可以给缺省值,而自定义类型给个缺省值还要定义一个全局变量,很麻烦。
那么为什么下面这样可以?
到时候初始化列表就直接用2去初始化,和上面的原因一样:同一个表达式连续步骤的构造,一般会被合二为一
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。
看看这个程序:
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();
}
这个程序输出的结果是什么呢?
A.输出1 1
B.程序崩溃
C.编译不通过
D.输出1 随机值
这里选D,这里先走了_a2,再走的_a1。
它是按照声明的顺序进行的,内存存储的就是声明的顺序。
在内存先走了_a2,再走的_a1。
所以声明和定义的初始化列表的顺序得保持一致。
2.3 explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。
C++11支持多参数
举个例子:
class A
{
public:
A(int a1, int a2)
:_a1(a1)
,_a2(a2)
{}
private:
int _a1;
int _a2;
};
int main()
{
A aa1 = { 1, 2 };
const A& aa2 = { 1, 2 };
return 0;
}
如果不想这样就加一个 explicit
关键字
explicit修饰构造函数,禁止类型转换
虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用explicit
修饰,具
有类型转换作用
用explicit修饰构造函数,将会禁止构造函数的隐式转换。
class A
{
public:
A(int a1, int a2)
:_a1(a1)
, _a2(a2)
{}
private:
int _a1;
int _a2;
};
class B
{
private:
// 缺省值
int _a = 1;
int* _p = (int*)malloc(4);
A aa1 = { 1,2 };
};
缺省值有这些写法:
3. static成员
3.1 概念
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。
面试题:实现一个类,计算程序中创建出了多少个类对象。
就是统计构造,构造函数调用了多少次。
得用一个全局变量
int n = 0;
class A
{
public:
A()
{
++n;
}
A(const A& aa)
{
++n;
}
A Func()
{
A aa;
return aa;
}
int main()
{
A aa1;
A aa2;
Func();
cout << n << endl;
return 0;
}
但这个代码在Debug和Release结果不一样。
Release做了代码的优化。
如果把n封装到类里面去,这里加加的n可能不同,所以给一个静态的n,但静态的不能给一个缺省值,因为不是属于某一个对象,属于所有对象,属于整个类。所以它得在类外面定义。
class A
{
public:
A()
{
++n;
}
A(const A& aa)
{
++n;
}
private:
// 声明
static int n;
};
// 定义
int A::n = 0;
A Func()
{
A aa;
return aa;
}
int main()
{
A aa1;
A aa2;
Func();
return 0;
}
想访问就就突破类域和访问界定符来访问。
class A
{
public:
A()
{
++n;
}
A(const A& aa)
{
++n;
}
//private:
// 声明
static int n;
};
// 定义
int A::n = 0;
A Func()
{
A aa;
return aa;
}
int main()
{
A aa1;
A aa2;
Func();
cout << aa1.n << endl;
cout << aa2.n << endl;
cout << A::n << endl;
return 0;
}
如果是私有的就不能这样访问,可以提供一个共有的成员函数。
class A
{
public:
A()
{
++n;
}
A(const A& aa)
{
++n;
}
// static成员函数没有this指针
static int GetN()
{
return n;
}
private:
// 声明
static int n;
int a = 0;
};
// 定义
int A::n = 0;
A Func()
{
A aa;
return aa;
}
int main()
{
A aa1;
A aa2;
Func();
cout << aa1.GetN() << endl;
return 0;
}
static成员函数没有this指针
3.2 特性
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
有问题请指出,大家一起进步!!!