在深度探索类的六大天选之子中,我们学习了类和对象的构造函数,知道了其可以用来初始化成员变量,也学了一些它的相关语法特性,但是C++中的构造函数真的就只是这样吗?本模块我们继续来谈谈有关构造函数的一些知识点
_a1
和_a2
属于【声明】,还没有在内存中为其开辟出一块空间以供存放,真正开出空间则是在【定义】的时候,那何时定义呢?也就是使用这个类A去实例化出对象的时候class A {
public:
int _a1; //声明
int _a2;
};
int main(void)
{
A aa; // 对象整体的定义,每个成员什么时候定义?
return 0;
}
const int _x;
const
修饰的变量有哪些特点const int i;
const
进行修饰的,不过编译后报出了错误说【必须初始化常量对象】,因为对于const
修饰的变量在声明的时候是必须要去进行初始化的,也就是要给到一个值现在我们就可以来聊聊有关上面的成员变量
_x
为什么没有被初始化的原因了
_a1
、_a2
、_x
都属于内置类型的数据,所以编译器不会理睬,可是呢const
修饰的变量又必须要初始化,这个时候该怎么办呢╮(╯▽╰)╭有同学说:这还不简单,给个缺省值不就好了
但是现在我想问一个问题:如果不使用这个办法呢?你有其他方法吗?难道C++11以前就那它没办法了吗?
概念:在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
【初始化列表】:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
int main(void)
{
Date d(2023, 3, 30);
return 0;
}
可以通过调试来观察一下它到底是怎么走的
接下去我再来说说这一块的难点所在,准备好头脑风暴
_a1
和_a2
我给到了缺省值,写了初始化列表后,它们还会被初始化吗?class A {
public:
A()
:_x(1)
{}
private:
int _a1 = 1; //声明
int _a2 = 1;
const int _x;
};
也通过调试来看一下
可以看到,即使在初始化列表没有给到_a1
和_a2
的初始化,还是会通过给到的默认缺省值去进行一个初始化。根据上面所学,我给出以下的结论
好,接下去难度升级,请问初始化列表修改成这样后三个成员变量初始化后的结果会是什么呢? 会是1、2、1吗?
class A {
public:
A()
:_x(1)
,_a2(1)
{}
private:
int _a1 = 1; //声明
int _a2 = 2;
const int _x;
};
一样通过调试来看看
_a2
了,因为我在声明的时候给到了缺省值,然后初始化列表去进行定义的时候又去进行了一次初始化,最后的结果以初始化列表的方式为主接下去难度继续升级,请问下面这样初始化后的结果是多少?
_a1
和_a2
进行了++和- -,那此时会有什么变化呢?class A {
public:
A()
:_x(1)
,_a2(1)
{
_a1++;
_a2--;
}
private:
int _a1 = 1; //声明
int _a2 = 2;
const int _x;
};
如果对于上面的原理搞清楚了,那看这个就相当于是再巩固了一遍。也是一样,无论是否给到缺省值都会去初始化列表走一遍,若是构造函数内部有语句的话就会执行
清楚了初始化列表该如何使用,接下去我们来说说其相关的注意事项
const
修饰的成员变量和构造函数对于内置类型不做处理产生了一个冲突,因此祖师爷就提出了【初始化列表】这个概念_z
需要被初始化,它必须要引用一个值_b
class B {
public:
B()
:_b(0)
{}
private:
int _b;
};
class A {
public:
A()
:_x(1)
,_a1(3)
,_a2(1)
,_z(_a1)
{
_a1++;
_a2--;
}
private:
int _a1 = 1; //声明
int _a2 = 2;
const int _x;
int& _z;
B _bb;
};
那对于有参构造该如何去初始化呢?
通过调试来看看编译器是如何走的
看完了上面这一种,我们再来看看稍微复杂一些的自定义类型是否也遵循这个规则
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10) //全缺省构造
{
cout << "Stack()构造函数调用" << endl;
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
_size = 0;
_capacity = capacity;
}
//....
private:
DataType* _array;
size_t _size;
size_t _capacity;
};
class MyQueue{
public:
//默认生成构造函数
private:
Stack _pushST;
Stack _popST;
size_t _t = 1;
};
int main(void)
{
MyQueue mq;
return 0;
}
可能读者有所忘却,我们再通过调试来看一下
Stack(size_t capacity)
_t
,依旧会使用我给到的初始值1MyQueue()
:_pushST(10)
,_popST(10)
{}
可以通过调试再来看看
//无参构造
MyQueue()
{}
所以可以看出,对于【内置类型】不做处理,【自定义类型】会调用它的默认构造可以看出其实就是当前类构造函数的初始化列表在起作用
在看了MyQueue类各种初始化列表的方式后,其实也可以总结出一点,无论如何不管有没有给到缺省值,只要是显式地写了一个构造函数,就可以通过调试去看出编译器都会通过【初始化列表】去进行一个初始化
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();
}
但结果却和我们想象的不一样,_a1
是1,_a2
却是一个随机值,这是为什么呢?
_a2
再去初始化的_a1
,对于【内置类型】我们可以知道是编译器是不会去进行初始化的,那若是一开始使用_a1
去初始化_a2
的时候,那_a2
就会是一个随机值,但是_a1
却使用传入进来的形参a进行了初始化,那它的值就是1_a1
先进行初始化即可,就不会造成随机值的现象了现在你在翻上去把所有的调试图一幅幅看下来就可以发现出丝滑列表是存在顺序的,它的顺序不是在列表中谁先谁后的顺序,而是类的成员变量声明的顺序
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用
class Date
{
public:
Date(int year)
:_year(year)
{}
private:
int _year;
int _month = 3;
int _day = 31;
};
int main()
{
Date d1(2022);
Date d2 = 2023;
return 0;
}
依旧通过调试来看就会非常清晰,这种写法也会去调用构造函数
int i = 1;
double d = i;
i
会先去构造一个临时变量,这个临时变量的类型是[double]
。把它里面的值初始化为1,然后再通过这个临时对象进行拷贝构造给d
,这就是编译器会做的一件事[Date]
把它里面的year初始化为2023,然后再通过这个临时对象进行拷贝构造给到d2
,小陈:不是说构造函数有初始化列表吗?拷贝构造怎么去初始化呢?
//拷贝构造
Date(const Date& d)
:_year(d._year)
,_month(d._month)
,_day(d._day)
{}
刚才说到了中间会产生一个临时对象,而且会调用构造 + 拷贝构造,那此时我们在Date类中写一个拷贝构造函数,调试再去看看会不会去进行调用
小叶:但您是怎么知道中间赋值这一块产生了临时对象呢?如果不清楚编译器的优化机制这一块肯定就会认为这里只有一个构造
Date& d3 = 2024;
const
做修饰后,就不会出现问题了,这是为什么呢?const
类型修饰对象不会有问题但若是你不想让这种隐式类型转换发生怎么办呢?此时就可以使用到C++中的一个关键字叫做
explicit
explicit Date(int year)
:_year(year)
{}
对于上面所讲的都是基于单参的构造函数,接下去我们来瞧瞧多参的构造函数
//多参构造函数
Date(int year, int month ,int day = 31)
:_year(year)
,_month(month)
,_day(day)
{}
d1
没有问题传入了两个参数,但是若是像上面那样沿袭单参构造函数这么去初始化还行得通吗?很明显不行,编译器报出了错误
小冯:那要怎么办呀,对于一定要传入多参数进行构造的场景
{}
就可以了,可能你觉得这种写法像是C语言里面结构体的初始化,但实际不是,而是在调用多参构造函数Date d2 = { 2023, 3 };
const Date& d3 = { 2024, 4 };
那要如何去防止这样的隐式类型转换发生呢,还是可以使用到
explicit
关键字吗?
//多参构造函数
explicit Date(int year, int month ,int day = 31)
:_year(year)
,_month(month)
,_day(day)
{}
explicit
关键字做修饰,同样可以起到【禁止类型转换】的作用explicit
关键字依旧可以起到作用·explicit Date(int year, int month = 3,int day = 31)
:_year(year)
,_month(month)
,_day(day)
{}
explicit
修饰构造函数,将会禁止构造函数的隐式转换【概念】:声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化
面试题:实现一个类,计算程序中创建出了多少个类对象
count
,然后在可能产生构造的地方进行累加int count = 0;
class A {
A()
{
count++;
}
A(const A& a)
{
count++;
}
};
void func(A a)
{
count++;
}
int main(void)
{
A aa1;
A aa2(aa1);
func(aa1);
return 0;
}
但是编译后可以发现,count++
的地方都出现了报错,说是不明确的问题
count
是和【std库】中的count发生了冲突
那这个时候该怎么办呢?
有同学说:这还不简单,不要写成count不就可以了,改成Count
都可以
using namespace std;
展开了std的命名空间,在这里我们只需要部分展开即可using std::cout;
using std::endl;
此时通过调试再去观察就可以看出创建了多少个对象
count
又去++了几下,此时最后的结果就出现问题了可以看到对于上面这种问题在C语言中是无法避免的,因为count是一个全局变量,那么它的生命周期就是从定义开始到main函数结束的时候销毁,这任何地方都是可以访问到的,并且它还不具有常性可以做任意修改,这其实也就缺乏了一定的安全性
class A {
public:
A(int a = 0)
{
count++;
}
A(const A& a)
{
count++;
}
private:
int count = 0;
};
static
来修饰一下这个成员变量static int count = 0;
static
修饰的话,它就不会放到在【栈区】了,而是在【静态区中】此时就引出了static的第一条特性如下
但此刻我为其设定缺省值的时候却报出了这样的错误,似乎不能在这里为它初始化呢?
::
int A::count = 0;
那么就引出了static
的第二条特性
那此时我若是要在外界去访问一下这个静态成员呢?能不能做到
::
::
又说它是私有的无法访问
那么就引出了static
的第三条特性
那要怎么去访问呢?这里有两种方式
cout << A::count << endl;
cout << aa1.count << endl;
cout << aa2.count << endl;
【拓展一下】
->
,但其实并没有去产生一个解引用,因为count是一个静态成员变量,虽然形式上放在类内,但上面说过了它是存放在内存中的静态区,所以无法用this指针访问到这个countA* aa3 = nullptr;
aa3->count;
那么就引出了static
的第四条特性
[类名::静态成员]
或者 [对象.静态成员]
这些指令在代码段上面这样都可以访问是因为我将静态变量count设置成了公有,若一旦设置为私有的话,上面这些访问形式就都是非法的了
int GetCount()
{
return count;
}
可以看到成员函数都是用对象去调的,那我现在假设我没有对象呢【博主确实没有】。此时还有办法获取到类内的静态成员变量吗?
static
作为修饰即可static int GetCount()
{
return count;
}
::
便可以访问到,可以说这个【静态成员函数】是专门为静态成员变量而生的看来这个静态成员函数还挺厉害的,若是我现在类中又新增了一个普通成员变量,可以在里面进行访问吗?
通过运行可以看出似乎是不可以
那么就引出了static
的第五条特性
学习完上了上面这五条特性之后,来回答一下下面这三个问题吧
静态成员函数可以调用非静态成员函数吗?
可以看到静态成员函数也是调用静态成员函数的
非静态成员函数可以调用类的静态成员函数吗?
请问静态成员函数存放在哪里,静态区还是公共代码区?
在学习了C++中的静态成员相关知识后,我们通过一道在线OJ来练练手
链接:牛客JZ64 求1+2+3+…+n
1. 题目描述
2. 思路分析
有同学说:这还不简单,用个递归呗
在同学们冥思苦想后,有一位同学提到了我们刚学习的static成员,那我们就用它来试试
static int sum;
static int i;
i
,将累加和设置为sum
,它们均为静态变量,根据我们上面所学知识要将其在类的外部进行定义初始化int Count::sum = 0;
int Count::i = 1;
Count()
{
sum += i;
i++;
}
Count c[n];
sum
static int GetSum()
{
return sum;
}
return Count::GetSum();
3. 代码展示
最后展示一下整体代码和运行结果
class Count{
public:
Count()
{
sum += i;
i++;
}
static int GetSum()
{
return sum;
}
private:
static int sum;
static int i;
};
int Count::sum = 0;
int Count::i = 1;
class Solution {
public:
int Sum_Solution(int n) {
Count c[n];
return Count::GetSum();
}
};
说完static修饰成员变量和成员函数,这里再来补充一点有关static修饰变量的注意点,我们主要通过题目来进行讲解
class A {
private:
int a;
public:
const int b;
float* &c;
static const char* d;
static double* e;
};
A.a b c
B.b c
C.b c d e
D.b c d
E.b
F.c
【答案】:B
【解析】:
对初始化列表不了解的可以先看看C++ | 谈谈构造函数的初始化列表
b
、c
很明确要选上,对于d
而言,虽然它有const修饰,但前面又有[static]
作为修饰,所以是一个静态成员函数,不属于类,存放在静态区中,当程序开始执行的时候就被初始化了,对于e
而言也是同理,所以答案选择BC c;
int main()
{
A a;
B b;
static D d;
return 0;
}
A.D B A C
B.B A D C
C.C D B A
D.A B D C
【答案】:B
【解析】:
这题的话通过调试来看一下就很清楚了,主要是观察static
的修饰对局部变量的作用域和生命周期的更改
在一个cpp文件里面,定义了一个static类型的全局变量,下面一个正确的描述是:( )
A. 只能在该cpp所在的编译模块中使用该变量
B. 该变量的值是不可改变的
C. 该变量不能在类的成员函数中引用
D. 这种变量只能是基本类型(如int,char)不能是C++类类型
【答案】:A
【分析】:
static int c = 1;
class A {
public:
A()
{
cout << "A的构造函数" << endl;
}
void func()
{
cout << "A的成员函数" << endl;
cout << c << endl;
}
~A()
{
cout << "A析构函数" << endl;
}
private:
int _a = 1;
};
class B {
public:
B()
{
cout << "B的构造函数" << endl;
}
void func()
{
cout << "B的成员函数" << endl;
}
~B()
{
cout << "B析构函数" << endl;
}
private:
int _b = 1;
};
static B b
就可以知道其也可以为自定义类型即C++的类类型【总结一下】:
static
修饰的成员变量,存放在静态区,而不在栈区,是不属于当前类的,因此需要在类外初始化static
修饰的局部变量,其生命周期会延长,但作用域不会发生变化static
修饰的全局变量,只在当前编译模块中(即当前文件内)生效,其他文件不可访问。因此其作用域发生了变化,但是生命周期没有变化(从定义到结束都不会被释放)顺着上面这道OJ题,顺带讲一个语法,就是这个【匿名对象】
Count c[n]
,这是C99里面的变长数组,在VS上是不支持的,语言可以提出规范,但是编译器支不支持是编译器自己的选择int Sum_Solution(int n) {
//Count c[n];
Count* c = new Count[n];
return Count::GetSum();
}
int main(void)
{
Solution s;
int ret = s.Sum_Solution(100);
cout << ret << endl;
return 0;
}
但是你觉得为了调用一个函数而去定义一个对象,不觉得浪费资源吗?如果可以不实例化对象就调用函数该多好O(∩_∩)O
【语法格式】:类名()
int ret = Solution().Sum_Solution(100);
A(n)
返回的其实也是一个匿名对象,没有实例化出一个对象名A func(int n)
{
return A(n);
}
除了使用方法,还有一点要牢记的是:匿名对象的声明周期只有一行
接下去我们来聊聊有关C++中友元的一些知识
温馨提示:友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
对于友元函数来说,我在Date日期类综合实战中有讲起过
operator<<
重载成全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>>
同理friend
关键字class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
_cin >> d._year;
_cin >> d._month;
_cin >> d._day;
return _cin;
}
int main()
{
Date d;
cin >> d;
cout << d << endl;
return 0;
}
以下是它的一些特性【了解一下即可,友元这一块其实用太多不好】
const
修饰的就是this指针,只有非静态的成员函数才能用const修饰除了友元函数以外,还有一个东西叫做友元类,也就是一个类也可以是另一个类的友元
SetTimeOfDate
的这个函数中初始化Time类中的三个成员变量,可是呢_hour
、_minite
、_second
是属于Time类中私有的成员变量,那Date类要如何访问到呢?friend class Date
表示在Time类中声明Date类是我的友元类,它可以访问我的私有成员变量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;
};
然后来说说友元类的一些特性
可能以上面这样去理解太枯燥了,我通过几个现实生活中的小案例来
以上就是关于友元的一些小知识,了解一下即可,这个东西用是可以用,但是在一开始说了,友元会破坏类的封装性,因为C++使用类将成员变量和成员函数封装起来,就是为了不让外界轻易访问,但若是设置了友元的话就可以访问了,在我看来这其实是比较荒谬的
接下去我们来讲讲一个东西叫做【内部类】
【概念】:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限
_a
,其内部还有一个类B,这个类B就叫做内部类class A
{
private:
static int k;
int h;
public:
class B // B天生就是A的友元
{
private:
int _b;
public:
void foo(const A& a)
{
cout << k << endl; //OK
cout << a.h << endl; //OK
}
};
};
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元
::
接下去再来说说它的特性
cout << a.h << endl;
sizeof()
的大小就会不一样了举个例子帮助理解
class Solution {
private:
//作为Solution的内部类
class Count
{
public:
Count()
{
sum += i;
i++;
}
static int GetSum() {
return sum;
}
private:
static int sum;
static int i;
};
public:
int Sum_Solution(int n)
{
Count c[n];
return Count::GetSum();
}
};
int Solution::Count::sum = 0;
int Solution::Count::i = 1;
不过这样修改其实还不够优,可以通过我们上面说到过的,内部类天生就是外部类的友元这一特点,去再进行一度优化
::
return sum
即可,因为这就是Solution自身的成员变量,它当然可以访问,都不需要内部类向外提供静态成员函数class Solution {
private:
//作为Solution的内部类
class Count
{
public:
Count()
{
sum += i;
i++;
}
};
static int sum;
static int i;
public:
int Sum_Solution(int n)
{
Count c[n];
return sum;
}
};
int Solution::sum = 0;
int Solution::i = 1;
在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的
【在看本模块之前若是有读者还不了解拷贝构造机制的先去看看】
explicit
关键字的时候,我有提到下面这种写法会引发【隐式类型转换】,而且还画了对应的图示,中间会通过1构造产生一个A类型的临时对象,然后用再去调用拷贝构造完成拷贝。A aa1 = 1;
首先来看到的场景是【传值传参】,对象均是使用上面
aa1
//传值传参
void func1(A aa)
{
}
请你思考一下这种形式编译器还会像上面那样去优化拷贝构造吗
func1(aa1);
aa
,main函数中的aa1
也会析构,不做展示】
那如果直接传入一个3呢,会做优化吗?
func1(3);
aa1
对象】
接下去我传入一个A(3)
,会发生什么呢?
func1(A(3));
A(3)
就是一个很明显的有参构造,实例化出一个对象后那就是拷贝构造,但是这里因为编译器的优化,所以直接变成了一个构造接下去来看到的场景是【传引用传参】,传入的值还是上面的这三种,只是会通过传引用来接收
//传引用传参
void func2(const A& aa) //不加const会造成权限放大
{
}
那通过引用接收aa1
会发生什么呢?
func2(aa1);
aa
就是aa1
的别名,无需构造产生一个新的对象,也不用去拷贝产生一个,直接用形参部分这个就可以了,现在知道引用传参的好处了吧
那直接传3呢?又会发生什么?
func2(3);
aa
就是这个临时对象的别名,所以无需调用拷贝构造,所以也是当回到主函数中才调用析构函数,此时析构的就是这个临时对象const
做修饰,否则就会造成权限放大
那么A(3)
也是和上面同样的道理
func2(A(3));
看完【传值传参】和【传引用传参】,我们来总结一下
接下去我们来讲讲函数返回时候编译器优化的场景,首先是【传值返回】
//传值返回
A func3()
{
A aa;
return aa;
}
若是直接去调用上面这个func3(),会发生什么呢?
func3();
此处在函数调用的地方我使用一个对象去做了接收,那在上面【构造 + 拷贝构造】的基础上就会再多出一个【拷贝构造】,即为【构造 + 拷贝构造 + 拷贝构造】
A aa2 = func3();
这里可能比较抽象,我画个图来解说一下
aa
与A
不是同一个表示式,所以不会引发编译器的优化;对于第二个来说,因为又拿了一个A的对象作为接收,所以又会产生一个拷贝构造。在这里编译器就要出手了,它会觉得两个拷贝构造过于麻烦,所以会直接优化成一个=
便是拷贝构造;若是一个对象已经实例化出来了,使用=
便是赋值重载A aa2;
aa2 = func3();
然后来说说【传引用返回】,不过若是你知道引用返回的一些机制的话,就可以清楚我下面这样其实是错误的,因为
aa
属于局部变量,出了当前作用域会销毁,所以不可以使用传引用返回,具体以下细述
A& func4()
{
A aa;
return aa;
}
首先来看下直接调用的结果会是怎样的
func4();
那我若是用一个返回值去接收的话,此时就可以看出引用返回临时对象的问题了
A aa3 = func4();
_a
就是一个随机值A func4()
还记得上面讲到的【匿名对象】吗,也可以使用它返回哦,效率还不低呢!
//匿名对象返回
A func5()
{
return A(); //返回一个A的匿名对象
}
先调用一下看看会怎么样
func5();
A()
你可以就把它看做是一个表达式,一个【构造】+【拷贝构造】就被优化成了直接构造
如果用返回值去接收呢?编译器会优化到何种程度
A aa4 = func5();
aa4
,第二次是aa3
,第三次便是一开始就有的aa1
了,通过这么调试观察,希望你能真正看懂编译器的思维
而且可以观察到匿名对象返回也不会造成随机值现象,因为本质使用的还是【传值返回】,这里不可以使用【传引用返回】,因为匿名对象构建出来的也是一个临时对象,具有常性,会造成权限放大
看完了上面这一些系列拷贝对象时编译器的优化,我们来做一个总结
对象返回总结
函数传参总结
const
+ &
传参,减少拷贝的同时防止权限放大现实生活中的实体计算机并不认识,计算机只认识二进制格式的数据。如果想要让计算机认识现实生活中的实体,用户必须通过某种面向对象的语言,对实体进行描述,然后通过编写程序,创建对象后计算机才可以认识。比如想要让计算机认识洗衣机,就需要:
举个形象点的案例,这里我在初步讲解类和对象的封装思想中也有提到,也就是外卖系统的设计
在类和对象阶段,大家一定要体会到,类是对某一类实体(对象)来进行描述的,描述该对象具有那些属性,那些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化具体的对象
好,最后来总结一下本文所学习的内容
explicit
】就可以让你服服帖帖的static
关键字,它可帮了大忙,但面对类内的私有成员变量,属实访问不到。此时双胞胎兄弟静态成员函数就来了,解决了我们的燃眉之急。也用这两个兄弟解决了一道在线OJ题以上就是本文要介绍的所有内容,感谢您的阅读