C++的类和对象(下)

Hello,亲爱的小伙伴们,我又回来了,今天我们将继续学习C++的类和对象,这时我们学习类和对象的第三节课,掌握了前面的知识,我们就算是对C++有了基础的认识,好,废话不多说,开始我们今天的正题!

1.再谈构造函数

  • 之前我们在实现构造函数的时候,初始化成员变量主要使用函数体内赋值。构造函数的初始化还有一种方式就是使用初始化列表,初始化列表的使用方式是由一个冒号开始,接着是一个以逗号分割的数据成员列表。每个“成员变量”后面跟着一个括号来表示初始值或者是表达式
#include
using namespace std;
class Time
{ 
public:
Time(int hour)
:_hour(hour)
{
cout << "Time()" << endl;
}
private:
int _hour;
}

  • 每个成员变量在初始化列表只能出现一次,语法理解上初始化列表可以认为是 每一个成员变量定义初始化的地方。
  • 应用成员变量,const成员变量,没有默认构造的参数在类类型变量,必须放在初始化列表中初始化!否则编译器会报错!
  • C++11支持在成员的变量申明的地方提供缺省值,这个缺省值主要是用于给没有在初始化列表中显示的成员变量使用的!
  • 尽量使用初始化列表列表来初始化成员变量因为那些你不在初始化列表中显示的变量也还是会走初始化列表。如果这个成员的申明位置给定了缺省值,初始化列表会直接使用缺省值来进行成员的初始化。如果你没有给缺省值,对于没有显示在初始化列表中的元素,是否要初始化,就取决于编译器,C++没有进行明确的规定!对于没有显示在初始化列表中的元素,,通常会调用这个自定义类型成员的默认构造函数,如果没有默认构造会编译出错!
  • 初始化列表中按照成员变量在类中的申明顺序来进行初始化!跟成员在初始化列表出现的先后顺序无关!建议变量申明的顺序和变量在初始化列表中初始化中的顺序相同!

初始化列表的总结:

无论是否显示写初始化列表,每个构造函数都哟初始化列表;

无论是否在初始化列表中初始化·,每个成员变量都要走初始化列表; 

#include
using namespace std;
class Time
{ p
ublic:
Time(int hour)
:_hour(hour)
{
cout << "Time()" << endl;
}
private:
int _hour;
}
class Date
{
 public:
Date(int& x, int year = 1, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
,_t(12)
,_ref(x)
,_n(1)
{
// error C2512: “Time”: 没有合适的默认构造函数可⽤
// error C2530 : “Date::_ref” : 必须初始化引⽤
// error C2789 : “Date::_n” : 必须初始化常量限定类型的对象
}
 void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
Time _t; // 没有默认构造
int& _ref; // 引⽤
const int _n; // const
};
int main()
{
int i = 0;
Date d1(i);
d1.Print();
return 0;
}

应用习题 

学习了上面的知识,我们现在来解决一下下面的问题:

 下⾯程序的运⾏结果是什么()

A. 输出 1 1   B. 输出 2 2     C. 编译报错    D. 输出 1 随机值     E. 输出 1 2      F. 输出 2 1

#include
using namespace std;
class A
{ 
public:
A(int a)
:_a1(a)
, _a2(_a1)
{}
void Print() {
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2 = 2;
int _a1 = 2;
};
int main()
{
A aa(1);
aa.Print();
}

 根据上面的知识,我们可以先观察到什么呢?

 我们根据前面讲解到的知识点,我们可以看出,最终的答案,是要我们输出_a1 和 _a2的值,

但是,我们还是可以轻易的看出在类A中,成员变量 _a1 和 以后,_a2的申明顺序和在初始化列表中初始画的顺序不一致!

在上面的知识点中,我们讲到,在这种情况中,成员变量的初始化顺序,是按照成员变量在类中声明的顺序来的,所以我们就可以知道,首先要初始·化的变量是_a2 ,然后才是 _a1;

又因为,在初始化_a2的_a1并没有初始化,所以_a2就是随机值,_a1就是直接使用缺省值来进行变量的初始化,所以_a1的值是1。

所以最后,我们得答案就是 D

 2.类型转换

  • C++支持的内置隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。
  • 构造函数前面加上explicit就不再支持隐式类型转换。
  • 类类型的对象之间也可以进行隐式转换,需要相应的构造函数来支持。

 

#include
using namespace std;
class A
{ 
public:
// 构造函数explicit就不再⽀持隐式类型转换
// explicit A(int a1)
A(int a1)
:_a1(a1)
{}
//explicit A(int a1, int a2)
A(int a1, int a2)
:_a1(a1)
, _a2(a2)
{}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
int Get() const
{
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{ 
public:
B(const A& a)
:_b(a.Get())
{}
private:
int _b = 0;
};
int main()
{
// 1构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aa3
// 编译器遇到连续构造+拷⻉构造->优化为直接构造
A aa1 = 1;
aa1.Print();
const A& aa2 = 1;
// C++11之后才⽀持多参数转化
A aa3 = { 2,2 };
// aa3隐式类型转换为b对象
// 原理跟上⾯类似
B b = aa3;
const B& rb = aa3;
return 0;
}

 3.static成员变量

  • 用static修饰的成员函数,称之为静态成员函数 ,静态成员函数没有this指针。
  • 静态成员函数中可以访问其他的静态成员,但是不能访问非静态的,因为没有this指针!
  • 非静态成员函数,可以访问任意的静态成员变量,和静态成员函数!
  • 突破类域就可以访问呢静态成员,可以通过类名::静态成员或者对象.静态成员来访问静态成员变量或者是静态成员函数!
  • 静态成员也是类的成员,会受到public、protect、private访问限制符的修订。
  • 静态成员变量不能在声明的位置给缺省值进行初始化,因为缺省值是构造函数初始化列表的,静态成员变量不属于某个对象,不走某个类的初始化列表。

 

/ 实现⼀个类,计算程序中创建出了多少个类对象?
#include
using namespace std;
class A
{
 public:
A()
{
++_scount;
} A
(const A& t)
{
++_scount;
} 
~A()
{
--_scount;
} s
tatic int GetACount()
{
return _scount;
}
private:
// 类⾥⾯声明
static int _scount;
};
// 类外⾯初始化
int A::_scount = 0;
int main()
{
cout << A::GetACount() << endl;
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
cout << a1.GetACount() << endl;
// 编译报错:error C2248: “A::_scount”: ⽆法访问 private 成员(在“A”类中声明)
//cout << A::_scount << endl;
return 0;
}

3.匿名对象

  • 用类型(实参)定义出来的对象叫做匿名对象,相比之下我们定义的类型对象名(实参)定义出来的叫做有名对象!
  • 匿名对象的生命周期只有一行,一般临时定义定义一个对象当前就调用一下即可,就可以通过匿名对象! 

 

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(1);
A aa2(2);
// 匿名对象在这样场景下就很好⽤,当然还有⼀些其他使⽤场景,这个我们以后遇到了再说
Solution().Sum_Solution(10);
return 0;
}

4.对象拷贝时的编译器优化

  •  现代编译器为了能够尽可能的提升效率,在不受影响的正确性情况下会尽可能减少一些传参和返回值的过程中可以省略的拷贝!
  • 如何优化C++标准并没有严格的规定,各个编译器会根据情况进行自行的处理。当前主流的相对新一点的编译器对于连续一表达式步骤中的连续拷贝会进行合并优化,有些更新更激进的编译器还会进行跨行,化表达式的合并优化!

 

#include
using namespace std;
class A
{ 
public:
A(int a = 0)
:_a1(a)
{
cout << "A(int a)" << endl;
} A
(const A& aa)
:_a1(aa._a1)
{
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa)
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a1 = aa._a1;
} 
return *this;
} 
~A()
{
cout << "~A()" << endl;
}
private:
int _a1 = 1;
};
void f1(A aa)
{}
A f2()
{
A aa;
return aa;
} 
int main()
{
// 传值传参
A aa1;
f1(aa1);
cout << endl;
// 隐式类型,连续构造+拷⻉构造->优化为直接构造
f1(1);
// ⼀个表达式中,连续构造+拷⻉构造->优化为⼀个构造
f1(A(2));
cout << endl;
cout << "***********************************************" << endl;
// 传值返回
// 返回时⼀个表达式中,连续拷⻉构造+拷⻉构造->优化⼀个拷⻉构造 (vs2019 debug)
// ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,直接变为构造。(vs2022 debug)
f2();
cout << endl;
// 返回时⼀个表达式中,连续拷⻉构造+拷⻉构造->优化⼀个拷⻉构造 (vs2019 debug)
// ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,直接变为构造。(vs2022 debug)
A aa2 = f2();
cout << endl;
// ⼀个表达式中,连续拷⻉构造+赋值重载->⽆法优化
aa1 = f2();
cout << endl;
return 0;
}

好,我们今天的学习就到这里,咱们下期再见,拜拜!!

你可能感兴趣的:(c++,开发语言)