以下为本文的大纲:

前言
提示:以下内容均属博主个人整理
随着我们前面的学习,我们对C++的类和对象已经有了大致的了解,本篇将整理类和对象一些相对零碎且不容易理解的点。
提示:以下是本篇文章正文内容,下面案例可供参考
一、初始化列表
初始化列表是什么?
我们前面已经学过构造函数,也明白了它的行为:对类的实例化对象进行自动的初始化。
普及概念:函数体
函数体就是一个函数大括号里面的部分,例如我们的构造函数
Date(int year = 1 ,int month = 1,int day = 1)
{
//…123456
}
括号里面的内容就是我们俗称的这个函数的函数体。
我们都清楚,Date函数的函数体就是用来初始化的,这点毋庸置疑,但是,不仅仅函数体可以初始化,我们这里讲到的初始化列表,一样也可以起到初始化的作用,并且它的初始化方式更加的成熟。
那,使用初始化列表初始化该怎么操作呢?请看代码
class Date
{
public:
Date(int year = 1,int month = 1,int day = 1)
:_year(year)
,_month(month)
,_day(day)
{
//...
}
//...
private:
int _year;
int _month;
int _day;
}
int main()
{
Date d1(2025,8,17);
return 0;
}
从上述代码,我们可以看到。
①初始化列表走在构造函数的函数体前面,即初始化列表的初始化优先度高于函数体,也就是说,初始化列表先对变量初始化初始化,然后再轮到函数体去更改一些需要再次进行初始化的变量。
②初始化列表的语法,:······
——————————,…
冒号开始,逗号分行,用括号中的内容去初始化与之对应的变量。(括号中最终呈现的类型要与前面的变量的类型相匹配)
函数体不好吗?非得整一个初始化列表?它相比于函数体的优势在哪里?
记住一句话:存在即合理
咱们先明白一个大前提:构造函数的函数体是在成员变量初始化之后才执行的,先执行的是初始化列表,相当于初始化列表初始化变量与类实例化对象同时进行。
①const 修饰成员变量
class Date
{
public:
Date(int y = 1)
{
_y = y
}
private:
const int _y;
}
int main()
{
Date d1(5);
return 0;
}
上述变量_y使用函数体初始化是行不通的,函数体的初始化在初始化列表之后,相当于执行完初始化列表才执行函数体的内容。而我们的成员变量_y为const int型,其值不可以被修改的,要在定义_y的时候就得赋予一个值,而初始化列表的又与变量的定义一起执行,相当于创建_y出来的同时就已经被赋予了一个值,而函数体慢于初始化列表。
所以正确的代码如下:
class Date
{
public:
Date(int y = 1)
:_y(y)//先执行初始化列表,创建_y的同时立马赋值
{
//_y = 10;后面执行函数体再根据实际的情况对_y的值进行一个修改,例如这里就把_y修改成了10
}
private:
const int _y;
}
int main()
{
Date d1(5);
return 0;
}
②引用
引用也是需要立即给值的,我是谁的别名要在第一时间就得确认清楚,我是你的别名就一直是你的别名,代码如下:
class Date
{
public:
Date(int & x,int y)
:_x(x);//_x与x均是xx的别名
,_y(y)
{
//...这里再对_x和_y进行适当的修改
}
private:
int & _x;
const int _y;
}
int main()
{
int xx = 0;
Date d1(xx,5);
return 0;
}
③没有默认构造的类类型变量
class Time
{
public:
Time(int hour)
:_hour(hour)
{
//
}
private:
int _hour;
}
这个类是没有默认构造的,得我们自己在定义类变量时手动输入形参hour
的值来给我们的_hour赋值。那么,这种类在创建变量的时候也得使用初始化列表。
class Date
{
public:
Date()
:_t(5)
{
}
private:
Time _t;
}
int main()
{
Date d1;
return 0;
}
构造函数的初始化规则
上面为大家讲解了初始化列表,再结合之前学习的函数体知识,整个构造函数大体我们已经学完了。但是有列表也有函数体,难免初始化的规则有点杂乱,本小节帮大家梳理并总结规则。
先附上本小节的思维导图:

①显示定义最优先
显示定义就是在初始化列表直接给赋予值的就是显式定义,例如_year(2025),那就使用这个2025给_year赋值
②如果没有显示定义(也就是说,走完一整个初始化列表,没有显示给值),那就看看有没有给缺省值,有给,就使用给的缺省值
缺省值是在定义的时候给的,例如:
class Date
{
public:
Date(int month = 1)
:_month(month)
//...
private:
int _year = 5;//有给缺省值
int _month;//没有给缺省值
}
int main()
{
Date d1(10);//_year没有显示定义,使用缺省值5,_month有显示给值,使用我们传过去的值10
return 0;
}
③ 如果没有显示定义(显示给值),也没有给缺省值。那么对于内置类型的话,给多少值,具体看编译器,有可能给0,也有可能给随机值;对于自定义类型,调用其默认构造函数;对于const修饰变量、引用、和无默认构造的类类型,没有给显式定义与缺省值,编译器会报错,所以这一类必须照顾到位。
④这些步骤全部走完,才轮到函数体进行修改相应变量的值。
题目补充
博主筛选了一道题目,补充一下知识点。
题目如下:
class A
{
public:
A(int a)
:_a1(a);//②
,_a2(_a1)//①
{}
void Print()
{
cout<<_a1<<" "<<_a2<<endl;
}
private:
int _a2;
int _a1;
}
int main()
{
A a(2);
a.Print();
return 0;
}
按照大部分人的思路,输出的答案为:2 2。但,其实这是忽略了一个重要的知识点。
Tips:先声明的先初始化
什么意思呢?我们的目光锁定类A中的private内容,里面定义了这个类中的变量,我们可以看到先定义了_a2,然后再定义了_a1,按照先定义的先初始化逻辑,我们应先执行①语句(_a2(_a1)),也就是先对_a2进行初始化操作,根据我们所学的初始化列表知识,显然是用_a1去初始化_a2,但是我们的_a1还没有初始化,所以编译器给了_a1随机值,所以_a2也是随机值,_a1晚定义,所以再执行②语句(_a1(a)),a我们传的是2,所以用2去初始化_a1,所以输出出来的结果为:2 随机值。
二、类型转换
C++支持内置类型隐式类型转换成类类型对象,需要有相关内置类型为参数的构造函数,代码如下(示例):
class A
{
public:
A(int a1)//第一个构造
:_a1(a1)
{}
A(int a1,int a2)//第二个构造
:_a1(a1)
,_a2(a2)
{}
private:
int _a1 = 1;
int _a2 = 2;
}
int main()
{
A aa1 = 1;
aa1.Print();
const A&aa2 = 1;
A aa3 = {2,2};
return 0;
}
① A aa1 = 1,这里可以理解为1不是int型,而是隐式类型转换称A类型的对象,这个对象的临时对象拷贝给了aa1,然后调用了第一个拷贝构造,把1赋值给了_a1,所以打印出来的结构为1 2,_a2无显示传值,所以使用缺省值2
②const A&aa2 = 1,同样的道理,1是int型隐式类型转换称A类型的对象,而aa2这个引用就是引用这个1的临时对象,临时对象具有常性,生命周期较短,不可修改,所以前面加上const
③A aa3 = {2,2},与上面相似,{2,2}是一个A类型的对象,调用了第二个拷贝构造函数,_a2与_a1都接受了这个2的传递,所以两个变量都是2。
三.static成员变量
实现一个类,计算程序中创建了多少个该类对象
int _scount = 0;
class A
{
public:
A()
{
++_scount;
}
A(const A & t)
{
++_scount;
}
~A()
{
--_scount++;
}
private:
//..
}
int main()
{
A aa1;
A aa2(aa1);
//_scount = 0;
return 0;
}
我们在全局中定义一个变量用来计数,这方法确实可行,但是我们可以在程序的任意位置修改这个_scount的值,这就达不到我们的目的了,因为别人可以随意修改它的值。所以,static成员变量就诞生了。先来过一遍它的特点。
static成员的特点
1.static修饰的成员变量,称为静态成员变量,静态成员变量必须要在类外面初始化(全局域)。
2.静态成员变量为所有类对象共享,不属于某个具体的对象,不存在对象中,放在静态区中。(假如我先定义了一个a1变量,计数器变为了1,后来我定义了一个a2变量,计数器不会从0开始算,而是在1的基础上再增加1)
3.static修饰的成员函数,形参中没有this指针,可以说static修饰的成员函数专门来访问静态成员变量。
4.非静态的成员变量,可以访问任意的静态成员变量和静态成员函数。
5.静态成员也是类的成员,受访问限定符的影响。
6.静态成员变量不能给缺省值,因为缺省值是为了构造某个对象的成员变量的,静态成员变量不属于某个对象,属于整个类。
所以,代码示例如下:
class A
{
public:
A()
{
++_scount;
}
A(const A&t)
{
++_scount;
}
~A()
{
--_scount;
}
static int Getcount()//无this指针,无法访问非静态变量,专门访问静态变量
{
return _scount;
}
private:
static int _scount;//类里声明 ,定义在private中,不会被轻易访问并修改。
}
int A::_scount = 0;//类外定义
int main()
{
cout << A::Getcount()<<endl;
A a1,a2;
A a3(a1);
cout<<A::Getcount<<endl;
cout<<a1::Getcount<<endl;
return 0;
//A::_scount无法访问,定义在了private中,被藏起来了,外部无法直接修改。
}
输出结果为0 3 3。A::Getcount与a1::Getcount两种访问方式。无法直接访问_scount。
四.友元
一个类中的private变量一般外人是无法访问的,那么有没有什么办法可以访问到某个类的私有变量呢?
所以,这就是友元的用途,它可以让外界访问到某个类的私有变量。
我是你的友元,我就可以访问你的私有变量,就像我是你的朋友一样。
①友元提供了一种突破访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明或者类声明的前面加上friend,并且把友元声明放到一个类的里面。
②友元函数只是一个声明,它不是类的成员函数。
③友元函数可以在类的任意位置声明。
④一个函数,可以是多个类的友元函数。
⑤友元类中的成员函数都可以是另一个类的友元函数,都可以访问另一个类的私有和受保护的成员
友元是单向的,比如A是B的友元,B就不一定是A的友元。
友元函数
代码示例,如下:
class B;//我们在A类中声明了func函数称为A的友元函数,但是A中出现了(const B&bb),编译器不认识,所以要先声明以下class B,编译器就认识了。
class A
{
public:
A(int a1,int a2)
:_a1(a1)
,_a2(a2)
{
}
friend void func(const A& aa, const B& bb);//友元声明!!!,可以在类中任意位置声明。
private:
int _a1;
int _a2;
};
class B
{
public:
friend void func(const A& aa, const B& bb);//友元声明!!!
B(int b1, int b2)
:_b1(b1)
,_b2(b2)
{
}
private:
int _b1;
int _b2;
};
void func(const A& aa, const B& bb)
{
cout << aa._a1 << " " << aa._a2 << endl;
cout << bb._b1 << " " << bb._b2 << endl;
}
int main()
{
A aa1 = { 1,1 };
B bb1 = { 2,2 };
func(aa1, bb1);
return 0;
}
这里func函数想要访问aa1和bb1中的私有变量是做不到的,所以要把func函数的声明前面+friend放到A和B类中,这样这个函数就可以访问A和B类中的私有变量了。
友元类
代码示例,如下:
class A
{
friend class B;//友元声明,B是A的友元,B可以访问A的私有变量。反过来,不一定成立。
private:
int _a1;
int _a2;
}
class B
{
public:
void func(const A& aa)
{
cout << aa._a1 << " "<< aa._a2 <<endl;//可以访问A中的私有变量
}
private:
int _b1;
int _b2;
}
五.内部类
如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它只受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。
内部类默认是外部类的友元类。
假设这个内部类包在这个外部类的private下,那相当于是这个外部类的一个私有成员,外界也是访问不了的。
代码示例,如下:
//int A::_y = 1;
class A
{
private:
static int _y;
int _a1;
public:
A(int a1)
:_a1(a1)
{}
class B
{
public:
void func(const A&aa)
{
cout << _y <<endl;//直接访问
cout << aa._a1<<endl;
}
private:
int _b1;
}
}
int A::_y = 1;//在类后面定义才是正确的写法
int main()
{
A aa1 = 1;
A::B b;//想要实例化B类的对象,得先通过A类型,所以定义方式如左
b.func(aa1);
return 0;
}
这里我还犯了一个小错误,我们要知道,编译器是从上往下编译的,所以我们在定义类中的静态成员变量时要在类后面定义,不能在类前面定义,不然编译器不知道你这个变量和类是从哪里来的。
六.匿名对象
这部分较为简单。
我们以前定义的对象一般都是有名字的,现在我们将学习定义一个没名字的对象。
匿名对象的生命周期就在定义的这一行中,出了这一行,即销毁。
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << A() <<endl;
}
~A()
{
cout << ~A()<<endl;
}
void Print()
{
cout << _a << endl;
}
private:
int _a;
}
int main()
{
A a1;//有名对象
A();//匿名对象,生命周期在所属的这一行
A(1);//同样是匿名对象
}
那么,匿名对象有什么用处呢?就是我们单纯地想调用一个类的成员函数时,不需要去创建这个类对象时,我们就会使用匿名对象。
class Solution
{
public:
int Sum_Solution(int n)
{
return n;
}
}
int main()
{
Solution().Sum_Soution(10);
return 0;
}
上方代码中,我们只是想调用这个Sum_Solution这个函数,跟我们的成员变量无关,而这个函数又只能在这个类中,所以就调用一个匿名对象去使用我们的这个Sum_Solution函数。
总结
本章是对类和对象的一个收尾。希望能帮到大家。
1069

被折叠的 条评论
为什么被折叠?



