目录
一,再谈构造函数
1.1 构造函数体赋值
1. 2 初始化列表
1.21 自定义类型成员
1.22 const 成员变量
1.23 引用成员变量
1. 24 初始化列表的“坑”
1. 3 explicit 关键字
二,static 成员
2.1 概念
2.2 特性
三, 友元
3. 1 友元函数
3. 2 友元类
特点:
3. 3 内部类(了解)
3. 31 概念
四, 匿名对象(了解)
五, 编译器对拷贝对象的一些优化
1. 传值传参
2. 传值返回
3. 隐式类型
4. 一个表达式中,连续构造+拷贝构造->优化为一个构造
5. 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
6. 一个表达式中,连续拷贝构造+赋值重载->无法优化
结语
一,再谈构造函数
1.1 构造函数体赋值
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
下面是过往的成员初始化,在该情景下则不太合适:
#include<iostream>
using namespace std;
class Time
{
public:
Time(int hour = 10) // 对于在函数体中初始化,必须写缺省参数,否则编译报错。
{
_hour = hour;
}
private:
int _hour;
};
class Date
{
public:
Date(int year, int hour)
{
_year = year;
Time t(hour);
_t = t;
}
private:
int _year;
Time _t;
};
int main()
{
Date a(2023,2);
return 0;
}
1. 2 初始化列表
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
// 成员变量声明
int _year;
int _month;
int _day;
};
- 引用成员变量
- const成员变量
- 自定义类型成员(且该类没有默认构造函数时)
1.21 自定义类型成员
用初始化列表来完成,自定义类型初始化,看下面代码:
#include<iostream>
using namespace std;
class Time
{
public:
Time(int hour = 11) // 区别于函数内构造(见1.1代码,这里必须加),全缺省参数可加可不加。
{
_hour = hour;
}
private:
int _hour;
};
class Date
{
public:
Date(int year, int hour)
:_t(hour)
{
_year = year;
}
private:
int _year = 100; // 缺省值,c++11打了个补丁,在内置类型初始化没有给值值时,给缺省值。
Time _t;1
};
int main()
{
Date a(2023,2);
return 0;
}
所以结合的函数体内部初始化与列表初始化我们可以看出:
1. 如果是函数内部初始化,赋值前还是得初始化,然后赋值,中间需要几次初始化,比较繁琐。
2. 如果在初始化列表初始化,可以不用有全缺省的默认构造函数,直接显示初始化。
结论:
- 自定义类型成员推荐用列表初始化,没有全缺省参数的构造函数必须用列表初始化。
- 内置类型推荐写到初始化列表,也可以写到函数体内部,两者随意。(除非为了代码好看,就需要写成函数内部初始化)
1.22 const 成员变量
原因是 如:const int x 必须是在定义的地方初始化。(我是这么理解的,这跟const xx类型的权限性质有关)
class Date
{
public:
Date(int year, const int x)
: _z(x)
{
_year = year;
}
private:
int _year;
const int _z;
};
int main()
{
int x = 0; // 用const修饰,就是想等传参;不修,则是缩小权限传参。
Date a(2023, x);
return 0;
}
1.23 引用成员变量
引用是另一个变量的“别名”,性质是不允许修改,所以必须在定义的时候初始化。
class Date
{
public:
Date(int year, int& x)
: _z(x)
{
_year = year;
}
private:
int _year;
int& _z;
};
int main()
{
int x = 0;
Date a(2023, x);
return 0;
}
1. 24 初始化列表的“坑”
看下面代码会有什么结果:
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:
解析:成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。
1. 3 explicit 关键字
首先我们查看一下以下代码:
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year)
{
_year = year;
}
private:
int _year;
};
void test()
{
Date x(10); // 直接调用构造函数
Date a = 100; // (隐式转化int->Date类型)构造一个Date类型的临时变量 +
//拷贝构造 + 优化(a的拷贝构造无法查看) —> 直接调用构造函数
}
(注意:关于编译器对拷贝的优化,本小节后面会讲)
而这次的 explicit关键字用来修饰构造函数,功能是:禁止类型转化。
explicit Date(int year)
{
_year = year;
}
所以Date a = 100,报错
二,static 成员
2.1 概念
使用场景:static修饰全局变量,在类外可以被任意访问,那如何设计出一个 只能让类访问的全局变量?
比如: 面试题:实现一个类,计算程序中创建出了多少个类对象。
class A
{
public:
A(int i)
:_i(i)
{
_scout++;
}
static int Getscout() // 类外无法取得static 成员, 所以需要一个类成员函数取得。
{
return _scout; // 在静态区中寻找_scout
}
A(A& k)
{
_i = k._i;
_scout++;
}
private:
static int _scout; // 声明。 (注意:缺省值为初始化列表提供的,而static成员是在类外定义)
int _i = 0;
};
int A::_scout = 0; // 初始化, A::通过类域定义----目的是突破类的限制
int main()
{
A a(1);
A b(2);
A c(3);
// 静态成员公有
//cout << c._scout << endl;
//cout << A::_scout << endl; // 两种方式并非去访问类,而是为了突破类域,去静态区去寻找
// 静态成员私有
cout << c.Getscout() << endl; // 这里访问成员函数,然后在静态区中寻找静态成员。
cout << A::Getscout() << endl; // 通过类域访问static成员
}
2.2 特性
三, 友元
3. 1 友元函数
功能:友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
特点:
- 友元函数可访问类的私有和保护成员,但不是类的成员函数。
- 友元函数不能用const修饰。
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
- 一个函数可以是多个类的友元函数。
- 友元函数的调用与普通函数的调用原理相同。
运用如下:
class A
{
public:
friend int func(const A& a); // 友元声明
private:
int _i;
};
int func(const A& a) // 普通函数
{
return a._i;
}
3. 2 友元类
特点:
- 1. 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
- 2. 友元关系不能传递。(如果C是B的友元, B是A的友元,则不能说明C时A的友元。)
- 3. 友元关系不能继承,在继承时再给大家详细介绍。
- 4. 友元关系是单向的,不具有交换性。 (比如下面Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接
访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。)
下面就是第4点的实践:
class Time
{
friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
Time(int hour = 0, int minute = 0, int second = 0)
: _hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
void SetTimeOfDate(int hour, int minute, int second)
{
// 直接访问时间类私有的成员变量
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t; // 在Date中声明一个Time类,过去是无法在Time类外直接访问其私有成员。
};
3. 3 内部类(了解)
Java中用的比较多,而C++用的比较少,这里仅作了解。
3. 31 概念
通过一段代码来测试其内部,外部类关系:
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
class Time
{
public:
Time(int hour = 0, int minute = 0, int secoud = 0)
:_hour(hour)
, _minute(minute)
, _secoud(secoud)
{}
void func(Date& _d) // 内部内类,Date天生是Time的友元类,所以可以通过对象直接访问Date其内部私有成员。
{
_d._year = _hour;
_d._month = _minute;
_d._day = _secoud;
cout << _d._year << endl;
cout << _d._month << endl;
cout << _d._day << endl;
}
private:
int _hour;
int _minute;
int _secoud;
};
void fundate()
{
cout << _t._hour; // 向内部类直接访问私有失败
}
private:
int _year ;
int _month ;
int _day ;
};
int main()
{
Date::Time b;
Date z;
b.func(z);
return 0;
}
class A
{
public:
class B
{
public:
void func()
{
cout << z << endl; // 访问外部类中的静态变量
}
private:
int _b = 100;
};
private:
int _i = 10;
static int z;
};
int A::z = 10;
int main()
{
A::B a;
a.func();
}
class A
{
public:
class B
{
public:
private:
int _b;
};
private:
int _i;
};
int main()
{
cout << sizeof(A); //运行可知为4字节
}
四, 匿名对象(了解)
比如,让我们看下面代码:
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
};
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
class Solution {
public:
int Sum_Solution(int n) {
//...
return n;
}
};
int main()
{
A aa1;
// 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
//A aa1();
// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
A();
A aa2(2);
// 匿名对象在这样场景下就很好用,当然还有一些其他使用场景,这个我们以后遇到了再说
Solution().Sum_Solution(10);
return 0;
}
五, 编译器对拷贝对象的一些优化
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "构造" << endl;
}
A(const A& aa)
:_a(aa._a)
{
cout << "拷贝构造" << endl;
}
A& operator=(const A& aa)
{
cout << "赋值构造" << endl;
if (this != &aa)
{
_a = aa._a;
}
return *this;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
void f1(A aa)
{}
A f2()
{
A aa;
return aa;
}
int main()
{
//1. 传值传参
A aa1;
f1(aa1);
cout << endl;
//2. 传值返回
f2();
cout << endl;
//3. 隐式类型,连续构造+拷贝构造->优化为直接构造
f1(1);
//4. 一个表达式中,连续构造+拷贝构造->优化为一个构造
f1(A(2));
cout << endl;
//5. 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
A aa2 = f2();
cout << endl;
//6. 一个表达式中,连续拷贝构造+赋值重载->无法优化
aa1 = f2();
cout << endl;
return 0;
}
1. 传值传参
结果:
2. 传值返回
class A
{
public:
A()
{
cout << "构造" << endl;
}
A(const A& z)
{
cout << "拷贝构造" << endl;
}
~A()
{
cout << "析构" << endl;
}
private:
int _a = 1;
};
A func()
{
A a; // 1次构造
return a; // 1次拷贝构造
}
int main()
{
A k = func(); // 1次拷贝构造,但是编译器会优化,把2次拷贝构造优化为1次
return 0;
可见,1次构造,1次拷贝;但为啥会这样,我们以下面的图来解释:
3. 隐式类型
结果:
4. 一个表达式中,连续构造+拷贝构造->优化为一个构造
结果:
5. 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
结果:
6. 一个表达式中,连续拷贝构造+赋值重载->无法优化
总结: 代码优化是编译器的功能,在release版本下代码优化比debug版本优化更强,同时不同的编译器优化的程度也不同。
结语
本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论;如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力。