
🔥草莓熊Lotso:个人主页
❄️个人专栏:《C++知识分享》《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受。
🎬博主简介:

我们学习完了6种默认构造函数以及Date类的实现后。今天这篇博客中博主将会继续为大家分享类和对象的知识,主要会涉及初始化列表,类型转换,static成员函数,友元以及匿名对象,编译器的一些优化。

--之前我们实现构造函数时,初始化成员变量主要使用函数体内赋值,构造函数初始化还有一种方法,就是初始化列表。
初始化列表总结:

--上述的这些特点在举例说明中都会体现出来
#include<iostream>
using namespace std;
class Time
{
public:
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 a = 0;
//对象整体定义,再传参给引用
Date d1(a);
d1.Print();
return 0;
} //通过上面的初始化列表,我们之前MyQueue类的一些问题也可以得到解决
//这里就大概实现一部分,仅作为演示使用
#include<iostream>
using namespace std;
class Stack
{
public:
Stack(int n)//这里给的不是默认构造
{
cout << "Stack(int n)" << '\n';
}
};
class MyQueue
{
public:
//MyQueue(int n = 4)
//{
// //这里如果直接这样写会去调用Stack的默认构造函数
// //但是没有,所以这里会报错。
//}
//我们可以利用初始化列表有效解决这个问题
MyQueue(int n = 4)
:_st1(n)
, _st2(n)
{
}
private:
Stack _st1;
Stack _st2;
};
int main()
{
MyQueue q1;
return 0;
}//其实除了初始化列表以外,我们还可以这样玩
#include<iostream>
using namespace std;
class Time
{
public:
Time(int hour)
:_hour(hour)
{
cout << "Time()" << endl;
}
private:
int _hour;
};
class Date
{
public:
Date()
:_year(100)
{
}
void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
//注意:这里不是初始化,而是给的缺省值,这个缺省值是给初始化列表的
//每个成员变量都会走初始化列表,如果未显示在初始化列表,则会按缺省值初始化
//如果都没有,那内置类型可能就是随机值,之前提到过的那三种就是直接报错
int _year=1;
int _month=1;
int _day;
Time _t = 1;// 没有默认构造
const int _n=1; // const
int* _ptr = (int*)malloc(40);
};
int main()
{
Date d1;
d1.Print();
return 0;
}那为什么都使用了初始化列表了,还需要那个括号呢
我们来看看下面这个场景
#include<iostream>
using namespace std;
class A
{
public:
A(int n = 10)
:_a((int*)malloc(sizeof(int)* n))
, _size(0)
{
//上面的_a的空间是否开辟成功是不是需要在这里检查
//以及给空间初始化是不是也得在这里实现
if (_a == nullptr)
{
perror("malloc fail!");
exit(1);
}
memset(_a, 0, sizeof(int) * n);
}
private:
int* _a;
int _size;
};
int main()
{
A aa;
return 0;
}
代码与解析:
//我们最后再来看个题
//注意_a1,_a2谁先初始化,以及最后结果是什么
#include<iostream>
using namespace std;
class A
{
//初始化顺序跟出现在初始化列表的顺序无关,所以这里其实是_a2先初始化,_a1再初始化
public:
A(int a)
:_a1(a)
, _a2(_a1)
{
//_a2先初始化,此时_a1还没初始化,所以是随机值,最后_a2也就是随机值了
//_a1再初始化,缺省值和初始化列表显示出现符合,按初始化列表的来,也就是1。
}
void Print() {
cout << _a1 << " " << _a2 << endl;
}
private:
//初始化列表按照成员变量在类中的声明顺序初始化
//这里我们发现_a2先声明
int _a2 = 2;
int _a1 = 2;
};
int main()
{
A aa(1);
aa.Print();//1 随机值
}--我们之前接触过内置类型和内置类型之前的转换,那么我们现在再来看看内置类型和类类型,类类型之间的转换
--上述这些特点都会在举例说明中体现出来
#include<iostream>
using namespace std;
class A
{
public:
//构造函数explict就不再支持隐式类型转换了
//explict A(int a1)
//这里的构造函数参数有相关内置类型
A(int a1)
:_a1(a1)
{}
private:
int _a1 = 1;
int _a2 = 2;
};
void func(const A& aa=1)
{
}
class Stack
{
public:
void Push(const A& a)
{
}
};
int main()
{
//1.内置类型之间的隐式类型转换
int i = 1;
double d = i;
const double& ref1 = i;//这个我们之前也讲过,中间的临时对象具有常性
//2.内置类型和类类型之间的隐式转换
//需要有相关内置类型为参数的构造函数
//这样写是直接构造
A a1(1);
//这个才涉及到隐式类型转换
A a2 = 1;
const A& ref2 = a1;
const A& ref3 = 1;
//三种都可以,因为上面的函数参数是用过const修饰的(所以第二种也ok)
func(a1);
func(1);
func();
//我们再来看看别的使用场景
Stack s1;
//这样写有点麻烦了
A a3(3);
s1.Push(a3);
//我们可以直接这样,方便了很多
s1.Push(3);
return 0;
}//再来看看多参数转化以及类和类之间的隐式类型转换
#include<iostream>
using namespace std;
class A
{
public:
// 构造函数explicit就不再支持隐式类型转换
// explicit A(int a1)
A(int a1)
:_a1(a1)
{
cout << "A(int a1)" << '\n';
}
A(const A& aa)
{
cout << "A(const A& aa)" << '\n';
}
A(int a1, int a2)
:_a1(a1)
, _a2(a2)
{}
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()
{
//再来跟大家展示一个优化,上面没提到
// 构造
A a1(1);
// 这里的隐式类型转换其实就是,2为参数构造临时对象,临时对象拷贝构造a2 -> 优化为直接构造
A a2 = 2;
const A& ref1 = 3;
//C++11之后才支持多参数构造
A a3(1, 1);
A a4 = { 1, 1 };
const A& ref2 = { 1, 1 };
//Stack st1;
//st1.Push(a4);
//st1.Push({2,2});
//类和类之间的隐式类型转换,跟上面原理类似
B b1 = a3;
const B& ref3 = a3;
return 0;
}
--上述这些特点都会在举例说明中体现出来
#include<iostream>
using namespace std;
class A
{
public:
//非静态的成员函数,可以访问任意静态成员变量以及非静态的成员变量
A(int a = 0)
:_a1(a)
,_a2(a)
{
++_count;
}
A(const A& t)
{
++_count;
}
static int GetCount()
{
// _a1++; 不能访问非静态成员,因为static成员函数没有this
return _count;
}
private:
int _a1 = 1;
int _a2 = 1;
//public:
// 在类里面声明,且这里不可以用缺省值
static int _count;
};
//必须在类外面初始化
int A::_count = 0;
int main()
{
//静态成员不存在对象之中
A aa1;
cout << sizeof(aa1) << endl;
A* ptr = nullptr;
A aa2 = 1;
/*cout << ptr->_count<< endl;
cout << aa1._count << endl;
cout << A::_count << endl;*/
//变成私有的话,需要使用一下函数来获取了
cout << A::GetCount() << endl;
cout << aa2.GetCount() << endl;
cout << ptr->GetCount() << endl;
return 0;
}
class Sum{
public:
Sum()
{
_ret+=_i;
_i++;
}
static int Get_Sum()
{
return _ret;
}
private:
static int _ret;
static int _i;
};
int Sum::_i=1;
int Sum::_ret=0;
class Solution {
public:
int Sum_Solution(int n) {
Sum arr[n];
return Sum::Get_Sum();
}
};再来看个练习
#include<iostream>
using namespace std;
class A
{
public:
A()
{
cout << " A()" << endl;
}
~A()
{
cout << " ~A()" << endl;
}
};
class B
{
public:
B()
{
cout << " B()" << endl;
}
~B()
{
cout << " ~B()" << endl;
}
};
class C
{
public:
C()
{
cout << " C()" << endl;
}
~C()
{
cout << " ~C()" << endl;
}
};
class D
{
public:
D()
{
cout << " D()" << endl;
}
~D()
{
cout << " ~D()" << endl;
}
};
//构造顺序
//析构顺序
C c;
int main()
{
A a;
B b;
static D d;
return 0;
}--这个题大家可以自己思考一下,答案自己运行一下就出来了
--上述的这些特点在举例说明中都会体现出来
//友元函数
#include<iostream>
using namespace std;
//前置声明,否则A的友元函数声明那里编译器不认识B
class B;
class A
{
// 友元声明
friend void func(const A & aa, const B & bb);
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
// 友元声明
friend void func(const A& aa, const B& bb);
private:
int _b1 = 3;
int _b2 = 4;
};
void func(const A& aa, const B& bb)
{
cout << aa._a1 << endl;
cout << bb._b1 << endl;
}
int main()
{
A aa;
B bb;
func(aa, bb);
return 0;
}//友元类,声明和定义需要分离,不然就算前置声明也解决不了问题,因为找不到类里面的成员
#include<iostream>
using namespace std;
// xxx.h
//前置声明
class D;
class C
{
// 友元声明
friend class D;
public:
void func1(const D& dd);
private:
int _a1 = 1;
int _a2 = 2;
};
class D
{
friend class C;
public:
void func1(const C& aa);
void func2(const C& aa);
private:
int _b1 = 3;
int _b2 = 4;
};
// xxx.cpp
void C::func1(const D& dd)
{
cout << dd._b1 << endl;
}
void D::func1(const C& aa)
{
cout << aa._a1 << endl;
cout << _b1 << endl;
}
void D::func2(const C& aa)
{
cout << aa._a2 << endl;
cout << _b2 << endl;
}
int main()
{
C cc;
D dd;
dd.func1(cc);
dd.func2(cc);
return 0;
}--上述这些特点都会在举例说明中体现出来
//内部类
#include<iostream>
using namespace std;
class A
{
public:
//内部类
class B //B默认是A的友元类
{
public:
void fun(const A& a)
{
cout << _k << '\n';//ok
cout << a._h << '\n';//ok
}
private:
int _b1;
};
private:
static int _k;
int _h = 1;
};
int A::_k = 1;
int main()
{
//静态成员变量和内部类都不算
cout << sizeof(A) << '\n';//4
//受外部类类域限制和访问限定符限制
A::B b;
A aa;
b.fun(aa);
return 0;
}
class Solution {
class Sum {
public:
Sum() {
_ret += _i;
_i++;
}
};
public:
int Sum_Solution(int n) {
Sum arr[n];
return _ret;
}
private:
static int _ret;
static int _i;
};
int Solution::_i = 1;
int Solution::_ret = 0;--上述这些特点都会在举例说明中体现出来
匿名对象
先拿上面的为例
#include<iostream>
using namespace std;
class Solution {
class Sum {
public:
Sum() {
_ret += _i;
_i++;
}
};
public:
int Sum_Solution(int n) {
//Sum arr[n];//要变长数组,vs不支持
return _ret;
}
//加个clear和析构
void clear()
{
_i = 1;
_ret = 0;
}
~Solution()
{
cout << "~Solution()" << '\n';
}
private:
static int _ret;
static int _i;
};
int Solution::_i = 1;
int Solution::_ret = 0;
void fun(const Solution& s=Solution(),int i=1)//缺省值直接给匿名对象
{
}
int main()
{
Solution s;//有名对象
cout << s.Sum_Solution(10) << '\n';
s.clear();
//匿名对象的生命周期只在当前行
//Solution(); //匿名对象
//const 引用会延长匿名对象的生命周期,生命周期跟const 引用一样
//const Solution& ref = Solution();
cout << Solution().Sum_Solution(10) << '\n';//这样就可以直接一行搞定了
fun(Solution());
fun(s);
fun();
return 0;
}#include<iostream>
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)
{}
int main()
{
// 构造+拷贝构造 优化-> 构造
A aa1 = 1;
cout << "==================" << endl;
f1(aa1);
cout << "==================" << endl;
f1(1);
cout << "==================" << endl;
f1(A(1));
cout << "==================" << endl;
return 0;
}

// https://en.cppreference.com/w/cpp/language/copy_elision.html
#include<iostream>
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;
};
A f2()
{
// NRVO
/*A aa;
cout << &aa << endl;
return aa;*/
// URVO
return A(1);
}
int main()
{
A aa1 = f2();
cout << &aa1 << endl;
return 0;
}
A f2()
{
// NRVO
A aa;
cout << &aa << endl;
return aa;
// URVO
// return A(1);
}
int main()
{
//不推荐,破坏了编译器的优化
A aa1;
aa1 = f2();
cout << &aa1 << endl;
// 推荐
A aa2 = f2();
return 0;
}往期回顾:
《吃透 C++ 类和对象(中):拷贝构造函数与赋值运算符重载深度解析》
《吃透 C++ 类和对象(中):const 成员函数与取地址运算符重载解析》
《详解 C++ Date 类的设计与实现:从运算符重载到功能测试》
结语:本文深入讲解了C++类和对象的核心概念,包括初始化列表、类型转换、static成员、友元关系和匿名对象等特性。重点分析了初始化列表的使用规则(必须初始化引用、const成员等)、类型转换的隐式规则(explicit禁止隐式转换)、static成员的共享特性(类外初始化)、友元关系的单向性等特点,如果文章对你有帮助的话,欢迎评论,点赞,收藏加关注,感谢大家的支持。
✨把这些内容吃透超牛的!放松下吧✨ ʕ˘ᴥ˘ʔ づきらど