再谈构造函数
1. 初始化列表
初始化列表的格式:
//初始化列表的格式
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
//栈如何使用初始化列表的格式
class Stack
{
public:
//初始化列表和函数体内初始化可以混着来
Stack(int capacity = 4)
:_top(0)
,_capacity(capacity)
{
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
}
Stack(const Stack& st)
{
_a = (int*)malloc(sizeof(int) * st._capacity);
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
memcpy(_a, st._a, sizeof(int) * st._top);
_capacity = st._capacity;
}
void push(int x)
{
//扩容...
_a[_top++] = x;
}
~Stack()
{
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
为什么需要初始化列表???
class MyQueue
{
public:
//Stack没有提供默认构造的情况下,必须的在初始化列表初始化
//或者自己想要显示初始化的情况下,都需要使用初始化列表
MyQueue()
:_pushST(4)
,_popST(4)
{}
void push(int x)
{
//...
}
private:
Stack _pushST;
Stack _popST;
size_t _size = 0; //这里不是初始化,给的是缺省值,是在初始化列表的时候定义的
};
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
//const变量必须在定义的时候初始化
class B
{
//对象的每个成员什么时候定义呢?--初始化列表
//每个成员都要走初始化列表,就算不显示在初始化列表写,也会走初始化列表
//1.内置类型,有缺省值就用缺省值,没有缺省值就用随机值
//2.自定义类型,调用它的默认构造函数,没有就报错
B()
:_n(0)
,_m(2)
,_aa(10)
{
//不支持
//_n = 0;
}
private:
//const 成员 和 引用的成员,这两种的性质一样
const int _n; //声明
int _m = 0; //缺省值,没有显示传,就用缺省值,传了就不需要用
//并且缺省值也是在初始化列表的时候起作用的
A _aa; //这种情况如何初始化???初始化列表
};
int main()
{
//对象实例化时整体定义
//那么对象的每个成员什么时候定义呢???---构造函数,初始化列表的时候
//为什么又需要初始化列表呢???因为有些问题必须在定义的时候解决
//那么const成员Stack st1;
MyQueue q;
return 0;
}
【注意】
1.每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2.类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量
- const成员变量
- 自定义类型成员(且该类没有默认构造函数时)
- 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
4.成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。
总结:
1.尽量使用初始化列表初始化
2.一个类尽量提供默认构造。(提供全缺省的默认构造)
2. explicit关键字
通过下面代码理解explicit关键字
class Date
{
public:
//总结:单参数、半缺省、全缺省都可以隐式类型转换 C++98支持
// 1. 单参构造函数,没有使用explicit修饰,具有类型转换作用
// explicit修饰构造函数,禁止类型转换---explicit去掉之后,代码可以通过编译
explicit Date(int year)
:_year(year)
{}
/*
// 2. 虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用explicit修饰,具
有类型转换作用
// explicit修饰构造函数,禁止类型转换
explicit Date(int year, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
*/
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
void Test()
{
Date d1(2022);
//Date d1 = 2022; //隐式类型的转换,加了ecplicit就不允许隐式类型转换
//Date& d5 = 2022; //不支持,临时变量具有常性
//const Date& d5 = 2022;
Date d3(d1);
Date d4 = d1;
}
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//C++11之后支持多参数的隐式类型转换
Date d1 = { 2022, 10, 12 };
//等价于
Date d2(2022, 10, 12);
const Date& d3 = { 2022, 10, 12 };
return 0;
}
用explicit修饰构造函数,将会禁止构造函数的隐式转换。
3.static
影响变量的生命周期
1.局部变量
2.全局变量
3.static(不仅影响变量声明周期,还影响函数的链接属性)
4.手动开辟的变量
static变量分三种:
1.全局的static
2.局部的static
3.类中的static
(静态区,属于类,并且是类的每个对象共享的)
局部的静态变量和全局的静态变量有什么区别:
它们的生命周期都相同,都是整个程序中都存在
但它们的作用域不同,局部的静态变量只在这个函数中可以用,而全局的静态变量在全局范围内使用。
//静态成员
int N = 0;// 全局变量是有很大问题的,很容易被修改
class A
{
public:
//创建了多少个对象就是构造函数和拷贝构造函数调用了多少个
A(int a = 0)
:_a(a)
{
++N;
}
A(const A& aa)
:_a(aa._a)
{
++N;
}
//静态成员函数 特点:没有this指针,只能访问非静态成员变量
static int GetN()
{
//_a++; 不能访问非静态的成员变量,没有this指针
return N;
}
private:
int _a;
//类中的静态变量受到类域的控制
//所以构造函数不要初始化静态变量,不然每次使用构造函数的时候都会初始化一次
//static int N = 0; //这是声明不是初始化
static int N; //需要在类外初始化
};
//定义初始化
int A::N = 0; //生命周期是全局的,作用域受类域限制
//如果传引用会少拷贝构造一次
void F1(A aa)
{}
A F2()
{
A aa;
return aa;
}
int main()
{
A aa1(1);
A aa2 = 2; //构造+拷贝构造 -->优化成了构造
A aa3 = aa1;
//访问方式
/*cout << A::N << endl;
F1(aa1);*/
//cout << aa1.GetN() << endl;
//F2();
//A* ptr = nullptr;
//cout << ptr->N << endl; //不会报错,没有解引用
//为什么不用类对象就可调用呢?因为静态成员变量没有this指针
cout << A::GetN() << endl; //使访问不受类域的限制
}
使用场景
//要求类对象只能在栈上
class A
{
public:
static A GetObj(int a = 0)
{
A aa(a);
return aa;
}
//使用private,不让在类外创建函数
private:
A(int a = 0)
:_a(a)
{}
private:
int _a;
};
int main()
{
//static A aa1; //不允许
//A* ptr = new A;
//A aa2;
//此时静态函数就体现出来了它的价值
//A aa1 = GetObj(10); //这有一个问题,就是先有鸡还是先有蛋的问题,要调用函数必须通过对象来调用
A aa1 = A::GetObj(10); //静态成员函数不需要创建对象就可以调用该函数
return 0;
}
3.友元
友元分为:友元函数和友元类
友元函数说明
- 友元函数可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
友元类说明
- 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
- 友元关系是单向的,不具有交换性。
- 友元关系不能传递
- 如果C是B的友元, B是A的友元,则不能说明C时A的友元。
- 友元关系不能继承。
4.内部类
说明
特性:
1.内部类可以定义在外部类的public、protected、private都是可以的。
2.注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
3.sizeof(外部类)=外部类,和内部类没有任何关系。
//内部类
//相当于两个独立的类
//区别:B类的访问受A类类域的限定 和 访问限定符的限制
class A
{
private:
int _a;
static int k;
public:
class B //B类天生就是A类的友元 注意:A类不能访问B类
{
void foo(const A& a)
{
cout << k << endl;//OK
cout << a._a << endl;//OK
}
int _b;
};
};
int main()
{
cout << sizeof(A) << endl; //4
//这样才能找到B类
A::B bb;
return 0;
}
//C++中最好不要用内部类(被偷家),也不鼓励使用友元(破坏了封装)
4.匿名对象
//匿名对象
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a = 0)" << endl;
}
~A()
{
cout << "~A" << endl;
}
private:
int _a;
};
class Solution {
public:
int Sum_Solution(int n) {
//...
return n;
}
};
A F()
{
/*A ret(10);
return ret;*/
//直接构造匿名对象返回,并且可以优化
return A(10);
}
int main()
{
//有名对象 -- 生命周期在当前局部域
A aa0;
A aa1(1);
A aa2 = 2;//隐式类型转换
//A aa3(); 不支持,存在歧义
//匿名对象 -- 声明周期在当前这一行
A();
A(3);
//匿名对象的价值
//要调用这个函数必须先创建一个对象,通过对象来调用
//Solution so;
//so.Sum_Solution(10);
//此处匿名对象发挥了作用
Solution().Sum_Solution(10); //直接创建匿名对象来调用函数
return 0;
}
5.拷贝对象使得一些编译器优化
在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还
是非常有用的。
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
A(const A& aa)
:_a(aa._a)
{
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa)
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a = aa._a;
}
return *this;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
//void f1(A aa)
//{}
void f1(const A& aa)
{}
A f2()
{
/*A aa(10);
return aa;*/
return A(10);
}
//《深度探索C++对象模型》
int main()
{
//优化场景一
//A aa1 = 1; //A tmp(1) + A aa1(tmp) -->优化 A aa1(1); 构造+拷贝构造-->构造
//优化场景二
/*A aa1(1);
f1(aa1); */ //构造+拷贝构造
//f1(A(1)); //构造+拷贝构造 -->构造
//f1(1); //构造+拷贝构造 -->构造
//匿名对象具有常性 不能优化,直接只有一个构造
/*f1(A(1));
f1(1); */
//优化场景三
//f2(); //构造+拷贝构造
//A ret = f2(); //构造+拷贝构造+拷贝构造-->优化后 构造+拷贝构造
//A ret;
//ret = f2(); //这样是不能优化的
A ret = f2();//构造+拷贝构造+拷贝构造-->优化后 构造
return 0;
}