C++类和对象:初始化列表、static成员和友元

news/2024/12/30 0:08:25/

目录

一. 初始化列表

1.1 对象实例化时成员变量的创建及初始化

1.2 初始化列表

1.3 使用初始化列表和在函数体内初始化成员变量的效率比较

1.4 成员变量的初始化顺序

1.5 explicit关键字

二. static成员

2.1 static属性的成员变量

2.2 static属性的成员函数

三. 友元

3.1 友元函数

3.2 友元类


一. 初始化列表

1.1 对象实例化时成员变量的创建及初始化

以演示代码1.1中的日期类Date为例,Date类中有一个用户显示定义的构造函数,来实现在类实例化时对成员变量赋初值。但是,成员变量并不是在构造函数的函数体内部进行创建的,在进入构造函数的函数体内部之前,成员变量会先被给一个随机的值,然后到构造函数内部改变这个随机的值来让成员变量初始化为用户希望的值。

因此,我们可以认为,Date成员变量是先被创建出来(不进行初始化),然后在被赋初值的。

演示代码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;
};

1.2 初始化列表

先将成员变量创建出来,再初始化,相比于创建时直接初始化,效率相对较低,而且,对于引用、具有const属性以及没有默认构造函数的自定义类型成员变量,其必须在定义创建的同时完成初始化。因此,在函数体内部初始化成员变量就有其局限性。

为了实现在定义创建成员变量的同时进行初始化,C++引入了初始化列表。其语法格式为:以一个冒号开始(冒号在函数声明的后面),以逗号分隔成员变量,每个成员变量后面跟一个括号表示被初始化的值。

演示代码1.2通过初始化列表,实现了对日期类三个成员变量的初始化。

演示代码1.2:

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;
};

注意,下面三种类型的成员变量只能采用初始化列表进行初始化:

  1. 引用类型成员变量。
  2. 具有const属性的成员变量。
  3. 没有默认构造函数的自定义类型成员变量。

演示代码1.2:(三种只能使用初始化列表的成员变量的初始化)

class A
{
public:A(int a):_a(a){}private:int _a;
};class B
{
public:B(A& a, int& ref)//没有默认成员函数的内置类型、引用以及const属性成员变量//只能用初始化列表初始化: _a1(a), _ref(ref), _n(10){}private:A _a1;   //没有默认成员函数的内置类型int& _ref;  //引用const int _n;  //const属性成员变量
};

1.3 使用初始化列表和在函数体内初始化成员变量的效率比较

演示代码1.3创建一个含有默认构造函数的类AA,在类A中包含类型为AA的自定义成员变量,先后显示定义两种不同形式的默认构造函数:一是在函数体内部初始化自定义成员变量,二是在初始化列表初始化自定义类型成员变量。

为了方便观察两种情况下构造函数、拷贝构造函数和赋值运算符重载函数分别调用了几次,函数应输出相关信息。运行代码,可以看到,如果在函数体内部初始化,需要调用两次调用构造函数、一次调用赋值运算符重载函数,如果使用初始化列表初始化,仅需要调用构造函数和拷贝构造函数各一次。对于较大的自定义类型成员变量,使用初始化列表可以显著提高程序运行效率。

这是因为,使用初始化列表在成员变量被定义出来的同时完成了初始化,而在函数体内部初始化则将成员变量的定义和初始化拆分为两个步骤。

演示代码1.3:

class AA
{
public:AA(int a = 1, int b = 2)  //构造函数{cout << "AA(int a, int b)" << endl;_a = a;_b = b;}AA(const AA& aa)  //拷贝构造函数{cout << "AA(AA& aa)" << endl;_a = aa._a;_b = aa._b;}AA& operator=(const AA& aa)  //赋值运算符重载函数{cout << "AA& operator=(const AA& aa)" << endl;_a = aa._a;_b = aa._b;return *this;}private:int _a;int _b;
};class A
{
public://函数体内初始化//A(int c, int d, const AA& aa)  //这里会调用初始化列表创建_aa,但给随机初值//{//	_c = c;//	_d = d;//	_aa = aa;//}//初始化列表初始化A(int c, int d, const AA& aa): _c(c), _d(d), _aa(aa){}private:int _c;int _d;AA _aa;
};int main()
{AA aa1;A a1(10, 20, aa1);return 0;
}
图1.1  在函数体内初始化(左)和采用初始化列表初始化(右)时程序运行结果

总结:对于有默认构造函数的自定义类型成员变量,可以在函数体内部初始化,也可以使用初始化列表初始化,但为了提高程序运行的效率,一般建议采用初始化列表初始化。

1.4 成员变量的初始化顺序

成员变量初始化的顺序与声明顺序一致,与初始化列表的顺序无关。如演示代码1.4所示,虽然初始化列表中_a1在前_a2在后,但是_a2依然先被初始化,_a1初始化后的值并没有被赋给_a2。因此,程序的运行结果并不是1  1,而是1  随机值 。

建议:成员变量的声明顺序与初始化列表的顺序保持一致。

演示代码1.4:

class AA
{
public:AA(int a1 = 10, int a2 = 20):_a1(1), _a2(_a1)   //按照声明顺序,先初始化_a2再初始化_a1{}void Print(){cout << "_a1 = " << _a1 << endl;cout << "_a2 = " << _a2 << endl;}private:int _a2;int _a1;
};int main()
{AA aa(1,1);aa.Print();return 0;
}
图1.2  演示代码1.4的运行结果

1.5 explicit关键字

如果一个构造函数只有一个参数,或者除了第一个参数外每个参数都有默认的缺省值,那么,就可以通过隐式类型转换来实现对象的实例化。

演示代码1.5定义了一个类Year,通过Year y1 = 2023,实现通过隐式类型转换完成对象实例化。但是,某些时候我们不希望这种隐式类型转换的发生,C++为此提供了一个关键字explicit,在函数声明之前添加explicit,即可禁止这种隐式类型转换。

演示代码1.5:

class Year
{
public://函数前加explicit,禁止隐式类型转换explicit Year(int year = 2023);void Print();private:int _year;
};Year::Year(int year)  //构造函数: _year(year)
{cout << "Year(int year = 2023)" << endl;
}void Year::Print()
{cout << _year << endl;
}int main()
{Year y1(2020);//Year y2 = 2023;   //存在隐式类型转换y1.Print();//y2.Print();return 0;
}

二. static成员

2.1 static属性的成员变量

  • 静态(static)成员变量属于整个类,并非单独属于某个类对象,而是所有类对象共享。
  • 必须在类外部定义static成员变量和给定初始化值,在类内部只是声明static成员变量。

通过static成员变量,可以实现统计一个类被实例化的次数。如演示代码2.1所示,在类A中定义了一个static成员变量_s_count,每当构造函数或拷贝构造函数被调用时,执行++_s_count操作,表示创建了一个类对象。运行演示代码2.1,_s_count的值为3,表示进行了3次对象实例化,调用构造函数创建对象a1第一次、调用构造函数创建对象a2第二次、调用f()函数传参时拷贝构造形参第三次。

注意:static属性的成员变量存储在静态区,在使用sizeof计算类或类对象大小时,static成员不包含在其中。

演示代码2.1:(统计类实例化次数)

//统计类对象总共被创建的次数
//构造函数和拷贝构造函数都会创建类对象
class A
{
public:A(int a = 5)   //构造函数: _a(a){cout << "A(int a = 5)" << endl;++_s_count;}A(A& a)   //拷贝构造函数: _a(a._a){cout << "A(A& a)" << endl;++_s_count;}//非静态成员函数int GetSumCount(){return _s_count;}private:int _a;static int _s_count;
};int A::_s_count = 0;   //初始化静态成员变量void f(A a)
{ }int main()
{A a1;A a2 = 1;f(a1);//通过对象调用函数获取_s_count的值cout << "_s_count = " << a1.GetSumCount() << endl;   return 0;
}
图2.1 演示代码2.1的运行结果

2.2 static属性的成员函数

static属性的成员函数严格来说并不能算是类的成员函数,其没有this指针,不能访问非static属性的成员变量或调用非static属性的成员函数。

静态成员函数有两种访问方式:(1)对象名.函数名(参数列表)、(2)类名::函数名(参数列表)

  • static成员也受访问限定符的限制,不能在类外部访问private和protect属性的static成员。
  • 虽然静态成员函数不能调用非静态成员函数,但是非静态成员函数可以调用静态成员函数。
class A
{
public:static void GetNum();   //静态成员函数private:int _a1 = 10;static int _a2;
};int A::_a2 = 20;   void A::GetNum()
{//cout << _a1 << endl;   //_a1为非静态成员变量,静态成员函数不能访问cout << "_a2 = " << _a2 << endl;
}int main()
{A a;A::GetNum();  //通过类名调用static属性函数A().GetNum();   //通过对象调用static属性函数,A()为匿名对象return 0;
}

三. 友元

友元可以分为友元函数和友元类。有一点要事先声明:友元是一种严重破坏封装的行为,一般建议友元要少用慎用。

3.1 友元函数

对于一个类中的private或protect属性成员,全局函数不能访问它们,但是,如果我们将某个全局函数声明为类的友元函数,那么这个全局函数就可以不受访问限定符的约束,随意访问类中的成员变量和成员函数。

友元函数的声明形式:friend 返回类型 函数名(参数列表)

演示代码3.1展示了友元函数的定义和使用,在类A中声明Print为A的友元,则全局函数Print可以访问A的私有属性成员变量并输出其值。这里还有一点需要注意:友元函数并不是某个类的成员函数,它是属于全局的。

演示代码3.1:

class A
{friend void Print(A& a);   //声明Print为A的友元
public:A(int a1 = 10, int a2 = 20)  //构造函数: _a1(a1), _a2(a2){}private:int _a1;int _a2;
};void Print(A& a)
{cout << "_a1 = " << a._a1 << endl;cout << "_a2 = " << a._a2 << endl;
}int main()
{A a;Print(a);  //Print为类A的友元return 0;
}
图3.1 演示代码3.1的运行结果

3.2 友元类

如果定义一个类A为类B的友元,那么这个类B中的任意一个成员函数就可以访问类A中的private或public属性成员。需要注意的是,友元类没有传递性和交互性,即:

  • 如果B是A的友元,C又是B的友元,不能认为C就是A的友元。
  • 假设B是A的友元,B可以访问A的非公有属性成员,我们也不能认为A是B的友元,即一般不能在类A中访问类B的非公有属性成员。

友元类的声明方式:friend class 类名

演示代码3.2定义了一个日期类Date和一个时间类Time,声明Date类是Time类的友元,那么,我们就可以在Date类中定义一个SetTimeOfDay函数,来设置Date类的Time类型成员变量。进入调试界面,打开监视窗口,看到Time类型成员_t的每个成员变量的值都依据调用SetTimeOfDay时传递的参数改为了12。

演示代码3.2:

class Time
{friend class Date;  //友元类声明public:Time(int hour = 1, int minute = 1, int second = 1): _hour(hour), _minute(minute), _second(second){}private:int _hour;int _minute;int _second;
};class Date
{
public:Date(int year = 2023, int month = 3, int day = 2)  //构造函数: _year(year), _month(month), _day(day){}//Date类的成员函数实现对Time类非公有属性成员的访问void SetTimeOfDay(int hour, int minute, int second){_t._hour = hour;_t._minute = minute;_t._second = second;}private:int _year;int _month;int _day;Time _t;
};int main()
{Date d;  //使用默认值完成类实例化d.SetTimeOfDay(12, 12, 12);  //时间设为12时12分12秒return 0;
}
图3.2 演示代码3.2

 


http://www.ppmy.cn/news/29412.html

相关文章

错误:PermissionError: [WinError 32] 另一个程序正在使用此文件,进程无法访问。“+文件路径“的解决方案

最近在使用python进行筛选图片的时候&#xff0c;想到用python里面的os库进行图片的删除。 具体筛选方法就是&#xff0c;删除掉图片长度或宽度小于100像素的图片&#xff0c;示例代码如下所示&#xff1a; for file in os.listdir(img_path):if file .split( . )[ - 1 ] j…

大数据处理学习笔记1.7 Scala类与对象

文章目录零、本节学习目标一、类&#xff08;一&#xff09;类的定义&#xff08;二&#xff09;类的实例化二、单例对象&#xff08;一&#xff09;单例对象概念&#xff08;二&#xff09;案例演示三、伴生对象&#xff08;一&#xff09;伴生对象概念&#xff08;二&#xf…

个人网站如何集成QQ快捷登录功能?

目录 一、网站集成QQ快捷登录的好处 二、网站接入QQ快捷登录具体步骤 &#xff08;1&#xff09;登录到QQ互联官网 &#xff08;2&#xff09;进行个人开发者认证 &#xff08;3&#xff09;创建网站应用 &#xff08;4&#xff09;填写网站资料 三、如何在本地开发环境…

第七章.集成学习(Ensemble Learning)—袋装(bagging),随机森林(Random Forest)

第七章.集成学习 (Ensemble Learning) 7.1 集成学习—袋装(bagging),随机森林(Random Forest) 集成学习就是组合多个学习器&#xff0c;最后得到一个更好的学习器。 1.常见的4种集成学习算法 个体学习器之间不存在强依赖关系&#xff0c;袋装&#xff08;bagging&#xff09;…

WuThreat身份安全云-TVD每日漏洞情报-2023-02-27

漏洞名称:OTFCC 缓冲区错误漏洞 漏洞级别:中危 漏洞编号:CVE-2022-35060,CNVD-2023-11996,CNNVD-202209-1527 相关涉及:OTFCC OTFCC 漏洞状态:EXP 参考链接:https://tvd.wuthreat.com/#/listDetail?TVD_IDTVD-2022-23648 漏洞名称:MuYucms 存在任意代码执行漏洞 漏洞级别:高危…

react hooks学习记录

react hook学习记录1.什么是hooks2.State Hook3.Effect Hook4.Ref Hook1.什么是hooks (1). Hook是React 16.8.0版本增加的新特性/新语法 (2). 可以让你在函数组件中使用 state 以及其他的 React 特性 貌似现在更多的也是使用函数式组件的了&#xff0c;重要 2.State Hook imp…

ChatGPT最牛应用,让它帮你更新网站新闻吧!

谁能想到&#xff0c;ChatGPT火了&#xff01;既能对话入流&#xff0c;又能写诗歌论文、出面试题、编代码&#xff0c;甚至还通过了谷歌面试拿到L3工程师offer&#xff0c;放在一年之前&#xff0c;没人相信这是当前AI能够达到的水平。ChatGPT自面世以来&#xff0c;凭借其极为…

树莓派centos7.9(armv7hl)添加软件源. 2023-03-02

1.添加 devtoolset-6 源 cat << EOF > /etc/yum.repos.d/devtoolset-6.repo [devtoolset-6] namedevtoolset-6 rebuild for armhfp baseurlhttps://armv7.dev.centos.org/repodir/devtoolset-6/ enabled1 gpgcheck0 EOF 2.启用 EPEL 软件源 cat << EOF >…