详解C++类和对象(下篇)

news2024/11/26 13:35:27

目录

一,再谈构造函数

 1.1 构造函数体赋值

 1. 2 初始化列表 

1.21 自定义类型成员 

1.22  const 成员变量

1.23 引用成员变量  

1. 24 初始化列表的“坑”

1. 3 explicit 关键字 

二,static 成员

2.1 概念 

2.2 特性 

三, 友元

3. 1 友元函数

3. 2 友元类 

特点: 

3. 3 内部类(了解)

3. 31 概念

四, 匿名对象(了解)

五, 编译器对拷贝对象的一些优化

1. 传值传参

2. 传值返回

3. 隐式类型

4.  一个表达式中,连续构造+拷贝构造->优化为一个构造

5. 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造

6. 一个表达式中,连续拷贝构造+赋值重载->无法优化

结语


一,再谈构造函数

 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;
};
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值 ,而不能称作初始化。因为 初始化只能初始 化一次,而构造函数体内可以多次赋值

下面是过往的成员初始化,在该情景下则不太合适:

#include<iostream>
using namespace std;
class Time
{
public:
	Time(int hour = 10)   // 对于在函数体中初始化,必须写缺省参数,否则编译报错。
	{
		_hour = hour;
	}
private:
	int _hour;
};

class Date
{
public:
	Date(int year, int hour)    
	{
		_year = year;
		Time t(hour);
		_t = t;   
	}
private:
	int _year;
	Time _t; 
};

int main()
{
	Date a(2023,2);
	return 0;
}

 

 1. 2 初始化列表 

      初始化列表:以一个 冒号开始 ,接着是一个以 逗号分隔的数据成员列表 ,每个 " 成员变量 " 后面跟一个放在括号中的初始值或表达式。
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成员变量
  • 自定义类型成员(且该类没有默认构造函数时)

1.21 自定义类型成员 

 用初始化列表来完成,自定义类型初始化,看下面代码:

#include<iostream>
using namespace std;
class Time
{
public:
	Time(int hour = 11)   // 区别于函数内构造(见1.1代码,这里必须加),全缺省参数可加可不加。
	{
		_hour = hour;
	}
private:
	int _hour;
};

class Date
{
public:
	Date(int year, int hour)  
		:_t(hour)
	{
		_year = year;
	}
private:
	int _year = 100; // 缺省值,c++11打了个补丁,在内置类型初始化没有给值值时,给缺省值。
	Time _t;1 
};

int main()
{
	Date a(2023,2);
	return 0;
}

所以结合的函数体内部初始化与列表初始化我们可以看出:

1. 如果是函数内部初始化,赋值前还是得初始化,然后赋值,中间需要几次初始化,比较繁琐。

2. 如果在初始化列表初始化,可以不用有全缺省的默认构造函数,直接显示初始化。

结论:

  • 自定义类型成员推荐用列表初始化,没有全缺省参数的构造函数必须用列表初始化。
  • 内置类型推荐写到初始化列表,也可以写到函数体内部,两者随意。(除非为了代码好看,就需要写成函数内部初始化)

1.22  const 成员变量

       原因是 如:const   int   x 必须是在定义的地方初始化。(我是这么理解的,这跟const xx类型的权限性质有关)

class Date
{
public:
	Date(int year, const int x)
		: _z(x)
	{
		_year = year;
	}
private:
	int _year;
	const int _z;
};

int main()
{
	int x = 0;  // 用const修饰,就是想等传参;不修,则是缩小权限传参。
	Date a(2023, x);
	return 0;
}

1.23 引用成员变量  

        引用是另一个变量的“别名”,性质是不允许修改,所以必须在定义的时候初始化。 

class Date
{
public:
	Date(int year, int& x)
		: _z(x)
	{
		_year = year;
	}
private:
	int _year;
	int& _z;
};

int main()
{
	int x = 0;
	Date a(2023, x);
	return 0;
}

1. 24 初始化列表的“坑”

看下面代码会有什么结果:

class A
{
public:
    A(int a)
       :_a1(a)
       ,_a2(_a1)
   {}
    
    void Print() {
        cout<<_a1<<" "<<_a2<<endl;
   }
private:
    int _a2;
    int _a1;
};
int main() {
    A aa(1);
    aa.Print();
}
/*A. 输出1  1
B.程序崩溃
C.编译不通过
D.输出1  随机值*/

 结果是 D:

解析:成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。

1. 3 explicit 关键字 

 首先我们查看一下以下代码:


#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year)
	{
		_year = year;
	}
private:
	int _year;
};

void test()
{
	Date x(10);   // 直接调用构造函数
	Date a = 100; // (隐式转化int->Date类型)构造一个Date类型的临时变量 + 
	              //拷贝构造 + 优化(a的拷贝构造无法查看) —> 直接调用构造函数
}

注意:关于编译器对拷贝的优化,本小节后面会讲) 

而这次的 explicit关键字用来修饰构造函数,功能是:禁止类型转化。

 explicit  Date(int year)
    {
        _year = year;
    }

所以Date a = 100,报错

二,static 成员

2.1 概念 

声明为 static的类成员称为 类的静态成员,用 static修饰的 成员变量,称之为 静态成员变量;用 static修饰成员函数,称之为 静态成员函数静态成员变量一定要在类外进行初始化
使用场景:static修饰全局变量,在类外可以被任意访问,那如何设计出一个 只能让类访问的全局变量

 比如: 面试题:实现一个类,计算程序中创建出了多少个类对象。

class A
{
public:
	A(int i)
		:_i(i)
	{
		_scout++;
	}

	static int Getscout()   // 类外无法取得static 成员, 所以需要一个类成员函数取得。
	{
		return _scout;      // 在静态区中寻找_scout
	}

	A(A& k)
	{
		_i = k._i;
		_scout++;
	}
private:
	static int _scout; // 声明。 (注意:缺省值为初始化列表提供的,而static成员是在类外定义)
	int _i = 0;
};
int A::_scout = 0;     // 初始化, A::通过类域定义----目的是突破类的限制

int main()
{
	A a(1);
	A b(2);
	A c(3);
	// 静态成员公有
	//cout << c._scout << endl;
	//cout << A::_scout << endl;     // 两种方式并非去访问类,而是为了突破类域,去静态区去寻找
	// 静态成员私有
	cout << c.Getscout() << endl;  // 这里访问成员函数,然后在静态区中寻找静态成员。
	cout << A::Getscout() << endl; // 通过类域访问static成员
}

2.2 特性 

1. 静态成员所有类对象所共享,不属于某个具体的对象,存放在静态区。
2. 静态成员变量必须在 类外定义,定义时不添加static关键字,类中只是 声明
3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问。
4. 静态成员函数 没有隐藏的 this指针,不能访问任何非静态成员,可以访问静态成员变量。
5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制(必要时需要用 成员函数访问私有的静态成员变量)。

三, 友元

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。友元分为: 友元函数友元类

3. 1 友元函数

 功能:友元函数可以直接访问类的私有成员,它是定义在类外部普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

特点:

  • 友元函数可访问类的私有和保护成员,但不是类的成员函数。
  • 友元函数不能用const修饰。
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
  • 一个函数可以是多个类的友元函数。
  • 友元函数的调用与普通函数的调用原理相同。

运用如下:

class A
{
public:
	friend int func(const A& a);  // 友元声明
private:
	int _i;
};

int  func(const A& a)   // 普通函数
{
	return a._i;
}

3. 2 友元类 

特点: 

  • 1. 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
  • 2. 友元关系不能传递。(如果CB的友元, BA的友元,则不能说明CA的友元。)
  • 3. 友元关系不能继承,在继承时再给大家详细介绍
  • 4. 友元关系是单向的,不具有交换性。 (比如下面Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接

访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。)

下面就是第4点的实践:

class Time
{
   friend class Date;   // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
 Time(int hour = 0, int minute = 0, int second = 0)
 : _hour(hour)
 , _minute(minute)
 , _second(second)
 {}
   
private:
   int _hour;
   int _minute;
   int _second;
};
class Date
{
public:
   Date(int year = 1900, int month = 1, int day = 1)
       : _year(year)
       , _month(month)
       , _day(day)
   {}
   
   void SetTimeOfDate(int hour, int minute, int second)
   {
       // 直接访问时间类私有的成员变量
       _t._hour = hour;
       _t._minute = minute;
       _t._second = second;
   }
   
private:
   int _year;
   int _month;
   int _day;
   Time _t;    // 在Date中声明一个Time类,过去是无法在Time类外直接访问其私有成员。
};

3. 3 内部类(了解)

Java中用的比较多,而C++用的比较少,这里仅作了解。

3. 31 概念

如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类成员。外部类对内部类 没有任何优越的访问权限。(外部与内部有这平等关系)
(注意: 内部类是外部类的友元类,外部类不是内部类的友元类。参见友元类的定义, 内部类可以通过外部类的对象参数来 访问外部类中的所有成员。但是外部类不是内部类的友元。)

 

通过一段代码来测试其内部,外部类关系:

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}

	class Time
	{
	public:
		Time(int hour = 0, int minute = 0, int secoud = 0)
			:_hour(hour)
			, _minute(minute)
			, _secoud(secoud)
		{}

		void func(Date& _d) // 内部内类,Date天生是Time的友元类,所以可以通过对象直接访问Date其内部私有成员。
		{
			_d._year = _hour;
			_d._month = _minute;
			_d._day = _secoud;
			cout << _d._year << endl;
			cout << _d._month << endl;
			cout << _d._day << endl;
		}
	private:
		int _hour;
		int _minute;
		int _secoud;

	};

    void fundate()
	{
		cout << _t._hour;  // 向内部类直接访问私有失败
	}

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

int main()
{
	Date::Time b;
	Date z;
	b.func(z);
	return 0;
}

特性:
1. 内部类可以定义在外部类的public、protected、private都是可以的。
2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
class A
{
public:

	class B 
	{
	public:
		void func()
		{
			cout << z << endl;  // 访问外部类中的静态变量
		}
	private:
		int _b = 100;
	};

private:
	int _i = 10;
	static int z;
};

int A::z = 10;
int main()
{
	A::B a;
	a.func();
}

3. sizeof( 外部类 )= 外部类,和内部类没有任何关系, 下面是验证代码
class A
{
public:
	class B
	{
	public:
	private:
		int _b;
	};
private:
	int _i;
};

int main()
{
	cout << sizeof(A); //运行可知为4字节
}

 

四, 匿名对象(了解)

 比如,让我们看下面代码:

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

 

五, 编译器对拷贝对象的一些优化

在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的。
下面是 VS在debug版本 下的测试代码:
class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "构造" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		cout << "拷贝构造" << endl;
	}
	A& operator=(const A& aa)
	{
		cout << "赋值构造" << endl;
		if (this != &aa)
		{
			_a = aa._a;
		}
		return *this;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

void f1(A aa)
{}

A f2()
{
	A aa;
	return aa;
}
int main()
{
	//1. 传值传参
	A aa1;
	f1(aa1);
	cout << endl;
	//2.  传值返回
	f2();
	cout << endl;
	//3. 隐式类型,连续构造+拷贝构造->优化为直接构造
	f1(1);
	//4. 一个表达式中,连续构造+拷贝构造->优化为一个构造
	f1(A(2));
	cout << endl;
	//5. 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
	A aa2 = f2();
	cout << endl;
	//6. 一个表达式中,连续拷贝构造+赋值重载->无法优化
	aa1 = f2();
	cout << endl;
	return 0;
}

 1. 传值传参

 

结果: 

 

 

2. 传值返回

class A
{
public:
	A()
	{
		cout << "构造" << endl;
	}

	A(const A& z)
	{
		cout << "拷贝构造" << endl;
	}

	~A()
	{
		cout << "析构" << endl;
	}
private:
	int _a = 1;
};

A func()
{
	A a;        // 1次构造
	return a;   // 1次拷贝构造
}

int main()
{
	A k = func(); // 1次拷贝构造,但是编译器会优化,把2次拷贝构造优化为1次
	return 0;

 可见,1次构造,1次拷贝;但为啥会这样,我们以下面的图来解释:

 3. 隐式类型

 

结果:

 

 

4.  一个表达式中,连续构造+拷贝构造->优化为一个构造

 

 结果:

 

5. 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造

 

 结果:

 

6. 一个表达式中,连续拷贝构造+赋值重载->无法优化

 

 总结: 代码优化是编译器的功能,在release版本下代码优化比debug版本优化更强,同时不同的编译器优化的程度也不同。

 

结语

本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论;如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力

 

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

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

相关文章

阿里云数据库RDS MySQL Serverless测评

文章目录 1. 背景2. 概念3. 操作步骤3.1 购买产品3.2 配置RDS账号3.3 设置网络访问权限3.4 连接实例 4. 与自建数据库相比的优势4.1 弹性设置4.2 监控比较直观4.3 报警比较灵活4.4 备份更安全、更方便 5. 总结 1. 背景 作为一枚程序员&#xff0c;在日常工作中少不了跟云产品打…

Linux C/C++并发编程实战(0)谈谈并发与并行

作为并发编程的第一讲&#xff0c;比较轻松&#xff0c;我们先来谈谈什么是并发和并行。 并发&#xff08;Concurrency&#xff09;是指一个处理器同时处理多个任务。 并行&#xff08;Parallelism&#xff09;是指多个处理器或者是多核的处理器同时处理多个不同的任务。 并发…

git rebase的理解

首先看下图 比如提价了三次&#xff0c;都是同一个文件的修改&#xff0c;有三次commit的信息 想把提交的版本信息变的好看一点&#xff0c;或者变成一次提交信息 // 这个表示要查看提交的三个版本并进行合并 git rebase -i HEAD~~~// 如何要合并多个版本 git rebase -i HEA…

媲美ChatGPT4的免费工具来了!傻瓜式教程不用魔法也能使用!

嗨呀 又是元气满满的一周啦 废话不多说直接进入正题&#xff0c;仅在注册时可能需要使用一些科学方法&#xff0c;使用完全无限制 优势 对中文的支持非常强大 无需魔法上网 不受限制 免费&#xff01;&#xff01;&#xff01; 实测优于ChatGPT3.5&#xff0c;略逊于4.0&…

vue-7:组件库(移动端vant)(PC端element)

移动端vant 插件安装&#xff08;按需导入&#xff09; 重启生效 # 通过 npm 安装 npm i unplugin-vue-components -D# 通过 yarn 安装 yarn add unplugin-vue-components -D 导入基于 vite 的项目&#xff1a; 如果是基于 vite 的项目&#xff0c;在 vite.config.js 文件中…

Git详细用法:Git概述 安装 常用命令 分支操作 团队协作 、GitHub、idea集成Git、idea集成GitHub、Gitee 码云、GitLab

0 课程介绍 说明&#xff1a; 在公司想要使用idea集成git&#xff1a; 首选需要下载安装Git&#xff08;查看第2章&#xff09;之后在中设置用户签名&#xff08;查看3.1&#xff09;然后在idea中集成Git&#xff08;查看第7章&#xff09;… 0.1 学习目标 第1章 Git 概述 …

高级语句(二)

一、VIEW&#xff08;视图&#xff09; 1、 概念 可以被当作是虚拟表或存储查询 视图跟表格的不同是&#xff0c;表格中有实际储存资料&#xff0c;而视图是建立在表格之上的一个架构&#xff0c;它本身并不实际储存资料。 临时表在用户退出或同数据库的连接断开后就自动消…

关于预处理器 sass 的超全用法

随着用户需求的增加&#xff0c;应用于页面的 css 代码越来越复杂越发臃肿难以维护&#xff0c;但是又没有 css 的替代品&#xff0c;css 预处理器作为 css 的扩展&#xff0c;出现在前端技术中。 sass 是 css 预处理器中常用的一种&#xff0c;它是一种动态样式语言&#xff0…

基于html+css图展示58

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

C++系列九:预处理功能

预处理功能 1. 宏定义2. 文件包含3. 条件编译4. 代码注释5. 预处理器注意事项6. 总结 预处理器是 C 编译器提供的一个工具&#xff0c;允许程序员在编译之前对源代码文件做出修改。它主要是根据在代码中命名实体的定义&#xff08;如宏、条件编译指令&#xff09;、源文件调用等…

分布函数有什么意义?

累积分布函数&#xff08;CDF&#xff09;有什么意义&#xff1f; 参考文献&#xff1a;姜咏梅. 浅析分布函数的意义与应用[J]. 科学与财富,2014(10):207-207,208. DOI:10.3969/j.issn.1671-2226.2014.10.183. 关于PMF、PDF、CDF的介绍&#xff0c;移步至我的笔记&#xff1a…

【SPSS】因子分析详细操作教程(附案例实战)

🤵‍♂️ 个人主页:@艾派森的个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞👍🏻 收藏 📂加关注+ 目录 因子分析 因子分析案例 因子分析

Clion开发STM32之OTA升级模块(一)

什么是OTA 百度百科解释个人理解&#xff1a;就是不通过烧录的方式&#xff0c;通过串口、网口、无线对主板运行的程序进行升级。减少后期的一个维护迭代程序的一个成本。 STM32的OTA升级模块的一个设计 程序启动的一个框架流程图(大致流程) FLASH的一个划分框图 BootLoader…

Nautilus Chain 或成未来最好的链上隐私生态

Nautilus Chain 目前仍旧处于测试网阶段&#xff0c;作为目前行业内首个&#xff0c;也是最受关注的 Layer3 模块化链&#xff0c;Nautilus Chain 在测试网早期阶段&#xff0c;整体就有着十分出色的数据表现。而该链有望在 6 月上线主网&#xff0c;面向更为广泛的开发者、用户…

分布式数据库设计与实现

分布式数据库设计与实现 摘要背景二期项目包括数据库选型分布式数据库设计数据集成测试部署分布式数据库扩展阅读 摘要 : 本文论述《金蚕工程》的分布式数据库的设计和实现。该项目的设计目标是实现企业间茧、丝等的合同交易&#xff08;交易规则和期货交易一样&#xff09;、…

【springcloud 微服务】springcloud openfeign使用详解

目录 一、前言 二、openfeign介绍 2.1 openfeign介绍 2.2 openfeign优势 三、Spring Cloud Alibaba整合OpenFeign 3.1 前置准备 3.2 代码整合过程 3.2.1 添加feign依赖 3.2.2 添加feign接口类 3.2.3 调整调用的方法 3.2.4 核心配置文件 3.2.5 接口模拟测试 四…

libevent介绍和使用

libevent介绍 libevent 是一个开源的事件通知库&#xff0c;它提供了一个跨平台的抽象接口&#xff0c;libevnet处理的事件包括网络IO事件&#xff0c;定时事件以及信号事件。它可以在不同的操作系统上使用&#xff0c;包括Linux、Windows和Mac OS X等。libevent 的主要目的是…

最全面的ChatGPT镜像网址:值得三连~~~

ChatGPT是一个基于人工智能的聊天机器人&#xff0c;它可以与用户进行自然语言交互。ChatGPT使用了最新的自然语言处理技术&#xff0c;包括深度学习和神经网络&#xff0c;以便更好地理解用户的意图和回答用户的问题。 ChatGPT可以回答各种问题&#xff0c;包括但不限于常见问…

69.建立手风琴组件第二部分

上节课我们构建了下图一样的基本样式&#xff0c;这节课我们来构建布局&#xff01; ● 建立阴影&#xff0c;并生成grid布局 .item {box-shadow: 0 0 32px rgba(0,0,0,0.1);#添加外阴影&#xff0c;为半透明黑色&#xff0c;大小为0&#xff0c;0&#xff0c;32pxpadding: 24p…

C++编译Boost库读写和解析JSON文件和XML文件(2023.5.14)

C编译Boost读写并解析JSON文件和XML文件 需求分析前提环境&#xff08;Win11、VS 2015&#xff09;1、Boost简介1.1 为何使用Boost&#xff1f;1.2 Boost快速上手&#xff08;Windows系统&#xff09; 2、Boost 安装和使用2.1 自己下载源码编译生成Boost库(v 1.82.0)2.2 从官网…