【 C++ 】类和对象的学习(三)

news2024/12/23 1:29:59

前言:

😘我的主页:OMGmyhair-CSDN博客

目录

一、初始化列表

二、类型转换

三、static成员

四、友元

五、内部类

六、匿名对象


一、初始化列表

当我们之前在写构造函数时,我们通常在构造函数内对成员变量进行赋值。但其实还有一种方法是对成员变量进行初始化,那就是初始化列表:

初始化列表的使用: 以⼀个冒号开始,接着是⼀个以逗号分隔的数据成
员列表,每个"成员变量"后⾯跟⼀个放在括号中的初始值或表达式。
但是函数体内也能对成员变量进行赋值,那么为什么还要初始化列表呢?
其实初始化列表可以认为是每个成员变量定义初始化的地方。
通过这句话我们来看看以下初始化需要注意的地方:
1.每个成员变量在初始化列表只能出现一次
其实很好理解,既然它是成员变量定义的地方,而成员变量不可以重复定义,因此也不能重复出现。
可以看到编译器对重复出现的成员变量进行了报错:已初始化。

2.引用成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进行初始化。

这个也很好理解。像这三种变量,它们本身必须在定义时就进行初始化。而初始化列表就是它们定义的地方,所以它们在初始化列表中必须进行初始化:

(1)引用变量

对于未定义时未初始化的引用变量,编译器要求对齐进行初始化。

(2)const成员变量

至于被const修饰的的变量,定义初始化后就不能再改变,也就不能再赋值,所以必须在定义时就进行初始化。

(3)没有默认构造的类类型变量

如果一个类没有默认构造,那么它的对象在定义时就必须进行初始化,否则没有合适的构造函数供它使用。

对这三类变量正确的初始化可以如下:

class A
{
public:
	A(int x)
	{
		_a = x;
	}

private:
	int _a;
};

class Date
{
public:
	Date(int& y,int date=20140101)
		: year(y),
		  _date(date),
		  a(2014)
	{

	}
private:
	int& year;
	const int _date;
	A a;
};

3.在C++11中支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的。

首先来看下面的一段代码:

class Date
{
public:
	Date(int year = 2014, int month = 12, int day = 3)
		: _year(),
		  _day(13)
	{

	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << "/" <<_date<<endl;
	}

private:
	int _year = 1000;
	int _month = 10;
	int _day = 10;
	int _date;
};

int main()
{
	Date d1(2024, 9, 6);
	Date d2;
	cout << "d1日期为:";
	d1.Print();
	cout << "d2日期为:";
	d2.Print();
	return 0;
}

来看看结果和你想的是否一样:

(1).首先,我们要区分构造函数参数列表的缺省值和成员变量声明处的缺省值

参数列表的缺省值是给你在初始化对象时调用构造函数是否需要传参用的,而成员变量声明处的缺省值,是用来给没有初始化的成员变量用的。

如果你参数列表的值没有赋值给成员变量,那么成员变量的值不会受到改变,这就是为什么d1初始化时是(2024,9,6),但是d1和d2的实际结果相同。

(2).为什么_year的值是0而不是1000?为什么_date是随机值?

在C++其实int也有构造函数,在初始化列表中,我们显式写了_year(),这一过程编译器会调用int的默认构造函数将_year赋值为0。

而_date我们没有在初始化列表中显式写出,则不会调用构造函数,那么就是随机值。

(3)._month为什么是10?_day为什么是13而不是10。

在初始化列表中,我们没有显式写出_month,这时如果在声明中有缺省值,就会用缺省值对_month进行赋值。

而_day在初始化列表中被我们显式地写出,进行了初始化,就不会再去使用在声明中的缺省值。

4.对于没有显式在初始化列表初始化的自定义成员变量,编译器会调用它们自己的默认构造函数。如果没有默认构造函数,编译器会报错。

无论我们是否显式地写出初始化列表,每个构造函数都有初始化。无论我们是否在初始化中显式写出某个成员变量,每个成员变量都会走一遍初始化列表。

总结一下成员变量走初始化列表的各种情况:

5.初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关。所以建议声明顺序和初始化列表顺序保持⼀致。

我们用一道题目来加深理解:

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

我们来看看上面代码的输出结果:

首先,初始化列表按照成员变量的声明顺序进行初始化。所以第一个进行初始化的成员变量是_a2,此时将_a1的值赋给_a2,但这个时候_a1还未进行初始化,因此_a1的值是随机值,所以_a2也是随机值。

第二个进行初始化的值是_a1,将a的值赋给_a1,而a的值为1,所以_a1的值也为1。

所以这道题的答案为1和随机值,选D。


二、类型转换

1.C++支持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。

在上面的代码中就发生了隐式类型转换。在A a=2这句代码中产生了一个A类型的临时变量,2赋值给这个临时变量,这个临时变量再赋值给a。换句话说,2构造了一个A类型的临时对象,这个临时对象拷贝构造给a,而编译器遇到连续构造加上拷贝构造会直接优化成直接构造,意思是直接对a进行构造不用临时对象。

我们还能对这个临时对象进行引用:

因为临时对象具有常性,而我们引用的就是临时对象,所以需要加上const。

在C++11后我们还能进行多个参数的类型转换:

2.我们还能进行不同类类型对象之间的转换,需要相应的构造函数支持

class B
{
};

class A
{
public:
	A(int x)
	{
		_a = x;
	}

	A(int x, int y)
	{
		_a = x;
		_aa = y;
	}

	A(B b)//隐式转换需要相应的构造函数支持
	{
		_b = b;
	}
private:
	int _a;
	int _aa;
	B _b;
};

int main()
{
	A a = { 1,1 };

	//不同类类型对象之间的隐式转换
	B ab;
	A aa = ab;
	return 0;
}

3.在构造函数前面加explicit就不再支持隐式转换:

可以看到加入了explicit关键字后,编译器对隐式转换进行了报错。


三、static成员

静态成员就是静态成员变量和静态成员函数,首先来看看它们共同的特性:

1.静态成员也是类的成员,受public、protected、private 访问限定符的限制。

2.突破类域就可以访问静态成员,可以通过类名::静态成员 或者 对象.静态成员 来访问静态成员变量和静态成员函数。

class A
{
public:
	static int GetX()
	{
		return _ax;
	}

	static int _az;

private:
	static int _ax;
	static int _ay;
};

int A::_ax=1;
int A::_ay=2;
int A::_az=3;

int main()
{
	A a;
	cout << A::_az << endl;
	cout << A::GetX() << endl;
	cout << a._az << endl;
	cout << a.GetX << endl;
}

来看在类中静态成员变量的几个特性:

1.用static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进行初始化。

class Date
{
private:
	static int _year;
	static int _month;
	static int _day;
};

int Date::_year;
int Date::_month;
int Date::_day;
2.静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。
可以看到在ra的大小比rb的大小小4个字节,因为A类型的对象没有将静态成员变量a的大小算进去。
3. 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是给构造函数初始化列表的,静态成员 变量不属于某个对象,不走构造函数初始化列表。
可以看到编译器对拥有缺省值的静态成员变量进行了报错:
再来看 静态成员函数的特性:
1.⽤static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。因为它没有this指针,所以静态成员函数可以访问其它的静态成员,但不能访问非静态的。
2.非静态的成员函数,可以访问任意的静态成员变量和静态成员函数。

使用静态成员的特性,我们来看三道题:

1.实现一个类,计算程序中创建出了多少的类对象

class Create
{
public:
	Create()//默认构造函数
	{
		_count++;
	}

	Create(const Create& c)//拷贝构造函数
	{
		_count++;
	}

	~Create()
	{
		_count--;
	}

	static int GetCount()
	{
		return _count;
	}

private:
	static int _count;
};

int Create::_count=0;

2.求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

class Sum
{
public:
    Sum()
    {
        _ret += _n;
        _n++;
    }

    static int GetRet()
    {
        return _ret;
    }

private:
    static int _n;
    static int _ret;
};

int Sum::_n = 1;
int Sum::_ret = 0;

class Solution
{
public:
    int Sum_Solution(int n) {
        //调用n个构造函数
        Sum* p = new Sum[n];
        delete[] p;//释放申请空间
        return Sum::GetRet();
    }
};

int main()
{

    cout << Solution().Sum_Solution(5) << endl;//使用匿名对象进行访问
    return 0;
}

3.设已经有A,B,C,D 4个类的定义,程序中A,B,C,D构造函数调用顺序为?()

设已经有A,B,C,D 4个类的定义,程序中A,B,C,D析构函数调用顺序为?()

C 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
E C A B D
F C D A B
首先看构造函数调用顺序,全局变量是在main函数之前就创建好的,至于局部静态变量是第一次运行到static D d才会初始化,那么调用顺序就非常清楚了,首先是全局变量C再是main函数中的A、B,直到运行到局部静态变量处才是D,故选择E选项。
再来看析构函数调用顺序,我们已知对象是后定义的先析构。局部静态变量的生命周期是全局的,而局部变量生命周期是在函数体内,肯定比全局和局部静态先一步销毁,所以是B、A这俩个先销毁调用析构。再来看全局和静态,它们的生命周期都是全局,所以谁后定义先析构,因此这俩个析构顺序是D、C,故选择B选项。

四、友元

友元提供了⼀种突破类访问限定符封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。友元分为:友元函数和友元类,在函数声明或者类声明的前面加friend,并且把友元声明放到⼀个类的里面。
首先来看友元函数的特性:
1.外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数。
2.友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
3.⼀个函数可以是多个类的友元函数。
4. 友元函数不能用const修饰,因为友元没有this指针
class Time;//Time的前置声明,否则Date不认识Time
class Date
{
	friend void Print(const Date& date, const Time& time);

public:
	Date(int year = 2014, int month = 12, int day = 13)
	{
		_year = year;
		_month = month;
		_day = day;
	}


private:
	int _year;
	int _month;
	int _day;
};

class Time
{
	friend void Print(const Date& date, const Time& time);
public:
	Time(int hour = 21, int minute = 18, int second = 13)
	{
		_hour = hour;
		_minute = minute;
		_second = second;
	}

private:
	int _hour;
	int _minute;
	int _second;
};

void Print(const Date& date, const Time& time)
{
	cout << date._year << endl;
	cout << time._hour << endl;
}

int main()
{
	Date a;
	Time b;
	Print(a,b);
	return 0;
}

再来看看友元类的特性:

1.友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。

2.友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。

3.友元类关系不能传递,如果A是B的友元, B是C的友元,但是A不是C的友元。

class Time
{
	friend class Date;
public:
	Time(int time=2129)
	{
		_time = time;
	}

private:
	int _time;
};

class Date
{
	friend class Age;
public:
	Date(int date=20240906)
	{
		_date = date;
		cout << _t._time << endl;//Date是Time的朋友,可以访问Time的私有
	}

private:
	int _date;
	Time _t;
};

class Age
{
public:
	Age(int age = 20)
	{
		_age = age;
		cout << _d._date << endl;//Age是Date的朋友,可以访问Date的私有
	}

private:
	int _age;
	Date _d;
};

但是这里的朋友是单向的,例如Date是Time的朋友,可以访问Time的私有。但是Time不是Date的朋友,不能访问Date的私有。

朋友关系不能连续。Date是Time的朋友,Age是Date的朋友,但是Age不是Time的朋友。


五、内部类

1.如果⼀个类定义在另⼀个类的内部,这个内部类就叫做内部类。内部类是⼀个独立的类,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。所以当我们计算外部类的大小时,不需要算上内部类
在下面示例中A类对象大小和C类对象相同,所以计算外部类大小时不需要算上内部类。
2.内部类默认是外部类的友元类。同样这种关系只是单向,内部类可以访问外部类的私有,但是外部类不能访问内部类的成员变量。
3.内部类本质也是⼀种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使用,那么可以考 虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地方都用不了。
像之前题目中的用类计算一个数的累和,可以将其改造成外部类和内部类。
class Solution
{
public:

    class Sum
    {
    public:
        Sum()
        {
            _ret += _n;
            _n++;
        }
    };

    int Sum_Solution(int n) {
        //调用n个构造函数
        Sum* p = new Sum[n];
        delete[]p;
        return _ret;
    }

private:
    static int _n;
    static int _ret;
};

int Solution::_n=1;
int Solution::_ret=0;

六、匿名对象

1.用类型(实参)定义出来的对象叫做匿名对象,相比之前我们定义的类型对象名(实参)定义出来的叫有名对象 。
2.匿名对象生命周期只在当前一行,⼀般临时定义⼀个对象当前用一下即可,就可以定义匿名对象。
当我们需要用到类里面的成员函数又不想初始化对象,就可以使用匿名对象来调用类里面的成员函数:
注意不要将匿名对象写成下面这样:
因为编译器无法识别是对象定义还是函数声明。



如果这篇文章有帮助到你,请留下您珍贵的点赞、收藏+评论,这对于我将是莫大的鼓励!学海无涯,共勉!😘😊😗💕💕😗😊😘




本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2112461.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

系统架构师考试学习笔记第三篇——架构设计高级知识(19)嵌入式系统架构设计理论与实践

本章考点&#xff1a; 第19课时主要学习嵌入式系统架构设计的理论和工作中的实践。根据新版考试大纲&#xff0c;本课时知识点会涉及案例分析题&#xff08;25分&#xff09;。在历年考试中&#xff0c;案例题对该部分内容都有固定考查&#xff0c;综合知识选择题目中有固定分值…

北大港中文腾讯提出ViewCrafter:一张图像就可以制作影视特效和游戏画面!

北大和港中文联合腾讯人工智能实验室提出了 ViewCrafter&#xff0c;这是一种利用视频扩散模型的先验从单个或稀疏图像合成一般场景的高保真新视图的新方法。 可以简单理解为将复杂的图像转换成新角度的图像版本。首先&#xff0c;它会使用特殊的算法来读取一张或几张图像&…

SpringBoot项目-实现简单的CRUD功能和分页查询

背景 本博文主要是创建了一个新的SpringBoot项目&#xff0c;实现基本的增删改查&#xff0c;分页查询&#xff0c;带条件的分页查询功能。是方便初学者学习后端项目的一个比较清晰明了的实践代码&#xff0c;读者可根据博文&#xff0c;从自己动手创建一个新的SpringBoot项目…

Scratch教师节 —— 感恩教师节

小虎鲸Scratch资源站-免费Scratch作品源码,素材,教程分享平台! Scratch教师节动画作品——感恩教师节 在这个特别的日子里&#xff0c;我们迎来了教师节。为了表达对老师们的感激之情&#xff0c;Scratch平台上的小朋友们用创意与热情制作了精彩的动画作品——“感恩教师节”。…

在国产芯片上实现YOLOv5/v8图像AI识别-【4.3】RK3588使用yolov8+bytetrack实现跟踪更多内容见视频

本专栏主要是提供一种国产化图像识别的解决方案&#xff0c;专栏中实现了YOLOv5/v8在国产化芯片上的使用部署&#xff0c;并可以实现网页端实时查看。根据自己的具体需求可以直接产品化部署使用。 B站配套视频&#xff1a;https://www.bilibili.com/video/BV1or421T74f 背景…

【Canvas与艺术】四叶花

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>386.四叶花</title><style type"text/css">.c…

GDB watch starti i files

watch break starti 在程序的最初开始运行的位置处断下来 ​​ i files 查看程序及加载的 so 的 sections ​​

【Ubuntu】环境准备

平时不怎么接触运维。linux的东西都快忘完了&#xff0c;正好最近腾讯云优惠&#xff0c;38元一年&#xff0c;优惠拉满&#xff0c;拿下一个玩一玩&#xff0c;可以当小程序的服务器&#xff0c;记录一些常用的操作&#xff0c;省的每次用的时候都想不起来 1.有一个linux系统…

对接后端download接口报未知异常错误

你一定遇到过这种情况&#xff0c;在一个项目中下载功能明明好好的&#xff0c;下载接口调用方法与前端调用方法封装的好好的&#xff0c;可是换了一个接口&#xff0c;竟然搞罢工了&#xff0c;类似下面这样的&#xff0c;你会不会无从下手&#xff0c;不知道该怎么办呢&#…

2.C_数据结构_线性表

线性表的描述 线性表就是若干数据的一个线性序列。 数学表达式&#xff1a; L&#xff1a;表名 a0~an-1&#xff1a;数据元素 n&#xff1a;表长&#xff0c;n>0是为非空表 二元描述形式&#xff1a; D&#xff1a;数据元素D用 ai 表示&#xff0c;这个 i 范围是0~n-1 …

【C++从练气到飞升】21---再谈哈希算法:位图 | 布隆过滤器 | 哈希切分

&#x1f388;个人主页&#xff1a;库库的里昂 ✨收录专栏&#xff1a;C从练气到飞升 &#x1f389;鸟欲高飞先振翅&#xff0c;人求上进先读书&#x1f389; 目录 ⛳️推荐 一、位图 1.1 一道面试题 1.2 位图的概念 1.3 位图的模拟实现 1.4 位图的应用 1.4.1 给定100亿…

双项第一!鼎捷强势领跑PLM市场

近日&#xff0c;国际数据公司IDC发布了《中国PLM市场分析及厂商份额&#xff0c;2023&#xff1a;创新左移》 报告数据显示鼎捷PLM2023年收入增长率39.5%&#xff0c;收入增速市场第一 鼎捷在多个细分行业市场中保持领先&#xff0c;在装备制造PLM领域市场份额达到7.9%市占率…

基于 rt-thread的I2C操作EEPROM(AT24C02)

一、AT24C02 The AT24C01A/02/04/08A/16A provides 1024/2048/4096/8192/16384 bits of serial electrically erasable and programmable read-only memory (EEPROM) organized as 128/256/512/1024/2048 words of 8 bits each.AT24C01A/02/04/08A/16A提供1024/2048/4096/8192…

Redis进阶(三)--Redis高性能底层原理

文章目录 第三章、Redis高性能底层原理一、持久化1、RDB&#xff08;1&#xff09;给哪些内存数据做快照?&#xff08;2&#xff09;RDB文件的生成是否会阻塞主线程&#xff08;3&#xff09;bgsave执的行流程&#xff08;4&#xff09;RDB文件&#xff08;5&#xff09;RDB的…

ios免签H5

1、windows下载mobileconfig文件制作工具&#xff0c;可在csdn搜索iPhone_Mobileconfig_Tool下载安装&#xff1b;IOS 从APP Store 下载Apple Configurator 2 2、用申请的域名SSL证书给mobieconfig文件签名&#xff0c;最好下载Apache证书&#xff0c;里面包含 AE86211.crt…

zabbix-高级应用(主被动监控、邮件告警、企业微信告警)

文章目录 zabbix-高级应用监控路由器交换机SNMP简单网络管理协议测试案例配置网络设备创建主机创建监控项测试监控项 自动发现什么是自动发现Discovery&#xff1f;配置自动发现1、创建自动发现规则2、创建Action动作&#xff08;发现主机后自动执行什么动作&#xff09;3、通过…

Python画笔案例-037 绘制彩色格子台阶

1、绘制彩色格子台阶 通过 python 的turtle 库绘制彩色格子台阶&#xff0c;如下图&#xff1a; 2、实现代码 绘制彩色格子台阶&#xff0c;以下为实现代码&#xff1a; """彩色格子台阶.py """ import turtle from random import randomturtle…

小杨做题c++

题目描述 为了准备考试&#xff0c;小杨每天都要做题。第1天&#xff0c;小杨做了a道题;第2天&#xff0c;小杨做了b道题;从第3天起&#xff0c;小杨每天做的题目数量是前两天的总和。 此外&#xff0c;小杨还规定&#xff0c;当自己某一天做了大于或等于m题时&#xff0c;接下…

KRTSt内嵌Lua脚本

KRTSt内嵌Lua脚本 Lua 简介 Lua是一门强大、高效、轻量、可嵌入的脚本语言。它支持多种编程架构&#xff1a;过程编程、面向对象编程&#xff08;OOP&#xff09;、函数式编程、数据驱动编程及数据描述。 Lua结合了简洁的过程语法和强大的数据描述结构&#xff08;基于关联数…

什么是网络准入控制系统?网络准入控制系统七大品牌介绍!

在当今信息化时代&#xff0c;企业网络安全面临着前所未有的挑战。网络准入控制系统&#xff08;NAC, Network Access Control&#xff09;作为一种重要的网络安全技术&#xff0c;扮演着守护企业网络安全大门的关键角色。网络准入控制系统通过对接入网络的设备进行身份验证、安…