类和对象 02【C++】

文章目录

  • 一、 构造函数(初始化列表)
    • 1. 初始化列表
    • 2. explicit 关键字
    • 3. static成员
  • 二、 友元
    • 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. 初始化列表

初始化列表: 以一个冒号开始,接着就是用逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或者是表达式

	//初始化列表
	Date(int year, int month, int day)
		:_year(year),
		_month(month),
		_day(day)
	{}

上述用于完成日期类的初始化

对于普通构造函数可能会:
在这里插入图片描述
当对象实例化后,对象要进行整体定义,每个成员要去调用构造函数,每个成员需要有定义初始化的地方,在C++中,构造函数的初始化列表,就是每个成员定义初始化的地方

在这里插入图片描述

成员变量定义初始化,上图_year _month _day 也会走初始化列表,只是没有给值,所以是随机值

在这里插入图片描述
上述给值,是缺省值,这个值是给初始化列表用的

  • 当初始化列表没有显示初始化的时候,回去调用缺省值(有缺省值的时候就初始化缺省值,否则就给随机值)
  • 当初始化列表初始化了,就直接初始化(不会去用缺省值了,因为初始化列表,已经初始化了)

所以当没有使用初始化列表,才会使用缺省值

在这里插入图片描述
先走初始化列表,再走函数体

能用初始化列表就用初始化列表

	Date(int year,int month,int day)
		:_year(year),
		_month(month),
		_day(day),
		_n(1)
	{}

我们知道一些成员定义的时候必须走初始化列表

const int _n;	//const修饰的变量
int&  _ref;			//引用必须在定义的时候初始化
class Date 
{
public:
	Date(int year,int month,int day,int& x)
		:_year(year),
		_month(month),
		_day(day),
		_n(1),
		_ref(x)
	{
	}
private:
	int _year;
	int _month;
	int _day;

	const int _n;	//const修饰的变量
	int& _ref;			//引用必须在定义的时候初始化
};

在这里插入图片描述

当我们不写,编译器自动生成的默认构造函数,对于内置类型不做处理,对于自定义类型去调用它的默认构造

自定义类型的成员变量必须放在初始化列表位置进行初始化(没有默认构造函数时)
在这里插入图片描述

下方代码,初始化列表中没有写对自定义成员的初始化,这里的自定义成员回去调用它自己的默认构造

class A 
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a = 0)" << endl;
	}
private:
	int _a;
};
class Date 
{
public:
	Date(int year,int month,int day,int& x)
		:_year(year),
		_month(month),
		_day(day),
		_n(1),
		_ref(x)
	{
	}
private:
	int _year;
	int _month;
	int _day;

	const int _n;	//const修饰的变量
	int& _ref;		//引用必须在定义的时候初始化
	A _aa;			//自定义类型的成员变量
};

当自定义成员没有默认构造,初始化列表中也没有定义初始化时
会报错(没有合适的默认构造函数可以用)
在这里插入图片描述
当自定义类型 初始化列表和默认构造都写的情况下,会走初始化列表

初始化列表注意点

  • 每个成员变量在初始化列表中只能初始化一次

  • 类中的以下成员必须在初始列表位置进行初始化

    • 引用成员变量
    • const成员变量
    • 自定义类型成员变量(没有写默认构造函数的情况下)
      上述三个不能在函数体内初始化
  • 其他成员变量可以在初始化列表,也可以在函数体内,建议在初始化列表,当然也可以混着使用

class Date 
{
public:
	Date(int year,int month,int day,int& x)
		:_year(year),
		_month(month),
		_day(day),
		_p((int*)malloc(sizeof(4)))
	{

		if (_p == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
	}
private:
	int _year;
	int _month;
	int _day;
	int* _p;
};
  • 成员变量在类中声明的顺序就是其在初始化列表中的初始化顺序,与初始化列表中的先后次序无关

分析下方代码:

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
因为成员变量在类中声明的顺序,就是初始化的顺序,在初始会去走初始化列表
先走_a2,但是此时_a1还是随机值,所以_a2为随机值,之后再走_a1,_a1的值初始化为1
打印结果 先打印_a1,再打印_a2 的结果
在这里插入图片描述

2. explicit 关键字

在C++中,explicit关键字主要用于类的构造函数声明中,它的主要目的是阻止隐式类型转换的发生。当一个类的构造函数被声明为explicit时,编译器将不会使用这个构造函数进行任何自动类型转换。

先看一下类型转化

//类型转换
	int i = 1;
	//double& b = i;	//error,类型转换产生临时变量,临时变量具有常性
	const double &b = i;	//所以这里要加上一个const
	
	//对于自定义类型
	// A& aa3 = 2;	//error,原因是 (单参数构造函数支持隐式类型转换,类型转换产生临时变量)aa3引用的不是2,而是临时变量,临时变量具有常性
	const A& aa3 = 3; //内置类型赋值给自定义类型,发生隐式类型转换

对于单参数构造函数支持隐式类型转换

class A 
{
public:
	//构造
	A(int x = 0)
		:_x(x)
	{}
	构造
	//explicit A(int x = 0)	//如果不想让隐式类型转换发生,使用explicit
	//	:_x(x)
	//{}
	//拷贝构造
	A(const A &aa)
	{
		cout << "	A(const A &aa)" << endl;
	}

private:
	int _x;
};
int main() 
{
	A aa1(6);	//构造
	//单参数构造函数支持隐式类型转换
	//这里5先去调用构造A的一个临时对象,然后再去拷贝构造
	A aa2 = 5;
	//不同类型之间进行赋值,中间会产生临时对象 (所以会构造+拷贝构造)
	//但是 有些编译器会优化 同一个表达式连续步骤的构造,一般会被合二为一,把拷贝构造给合并了
	return 0;
}  

在C++98中是不支持多参数隐式类型转换的,C++11是支持多参数隐式类型转换的

	D dd1 = {6,6};

上述的隐式类型转换,如果不想让其发生则需要,使用explicit 关键字

	explicit A(int x = 0)	//如果不想让隐式类型转换发生,使用explicit
		:_x(x)
	{}

给成员变量缺省值的一些情况

	//给缺省值
	int _a = 1;
	int* p = (int*)malloc(4);
	D dd1 = {1,2};

3. static成员

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化

例如:
实现一个类,计算程序中创建了多少个类对象

//static 成员
//实现一个类,计算程序中创建了多少个类对象

int  n = 0;	//用一个全局变量去计算构造函数走了多少次
class A 
{
public:
	A() 
	{
		++n;
	}
	A(const A& aa) 
	{
		++n;
	}
};
A func() 
{
	A aa;
	return aa;
}
int main() 
{
	A aa1;
	A aa2;
	func();
	cout << n << endl;
	//对于创建多少个类对象,我们可以去统计构造函数构造了多少次
	return 0;
}

对于上方代码,全局变量n是很容易让修改的,所以把 n封装,使其不被修改

// 上述的n容易被修改
// 所以把 n封装,使其不被修改
class A
{
public:
	A(){ ++n; }
	A(const A& aa){++n;}
	
	 //静态成员函数
	//静态成员函数和普通的成员函数的区别是,静态成员函数没有this指针
	//静态成员变量的特点是:比如定义一个全局的又想用类进行封装,那就可以定义静态成员变量
	//访问静态成员变量,提供对应的静态成员函数,来访问静态成员变量
	 static int GetN() 
	 {
		 return n;
	 }
private:
	//int n = 0; //这样是不能加上同一个n的
	//声明
	static int n;	//注意这里的n不是属于某一个对象,而是属于所有对象,属于整个类,要在类外面进行定义
};

//定义,静态成员变量一定要在类外进行初始化
int A::n = 0;

A func()
{
	A aa;
	return aa;
}
int main()
{
	A aa1;
	A aa2;
	func();
	//对于私有的情况下,获取n要提供一个类中的公有成员函数getter
	cout <<aa1.GetN()<< endl;
	cout << A::GetN()<< endl;	//静态成员函数允许类域访问
	//对于创建多少个类对象,我们可以去统计构造函数构造了多少次
	return 0;
}

注意:定义和初始化静态成员变量一定要在类外进行
在这里插入图片描述
所以,静态成员变量可以在类里面声明,但是定义和初始化是在类外面进行的在C++中,静态成员变量在类外部定义和初始化是为了确保它们在整个程序范围内唯一存在、正确初始化以及避免多重定义的问题

static成员小总结:

  • 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
  • 静态成员变量必须在类外定义,定义时不用加static关键字,类中只是声明
  • 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
  • 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  • 静态成员也是类的成员,也受访问限定符的限制

二、 友元

“友元”(Friend)是一个特殊的访问权限控制机制。
友元提供了一种突破封装的方式,为有些情况提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。

友元 分为 友元函数友元类

1. 友元函数

在重载操作符流插入 << 和流提取 >> 时,一般写成全局的函数才能完成。函数去访问成员变量时,受到封装的限制,要获取封装的成员变量,使用getter和setter来完成。

但是使用友元函数去封装类中声明,就可以获取到访问私有(private)和保护(protected)成员的权限
例如 重载操作符流插入 << 和流提取 >> 使用 友元去封装的类进行声明,声明时需要加friend关键字

friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in,Date& d);

那为什么 重载操作符流插入 << 和流提取 >> 要写成全局的函数呢?

因为如果写成 成员函数cout的输出流对象和隐含的this指针会抢占第一个参数的位置。this指针默认是第一个参数(左操作数),实际情况是cout的输出流对象需要是第一个参数位置才可以,所以要operator<<重载成全局函数。operator>>同理。

友元函数小总结:

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

2.友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员

//友元类
class Time
{
	//声明 日期类 为 时间类 的友元类,则日期类就可以直接访问时间类的私有成员变量
	friend class Date;
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 = 1970,int month = 1,int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
	void set_tmie_date(int hour,int minute,int second)
	{
		//可以直接访问时间类的成员变量
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}


private:
	int _year;
	int _month;
	int _day;

	Time _t;
};

友元类 小总结:

  • 友元关系是单向的,不具有交换性。
    如 上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行

  • 友元关系不能传递。如果C是B的友元, B是A的友元,则不能说明C时A的友元

  • 友元关系不能继承

三、 内部函数

一个类定义在另一个类的内部,这类叫做内部类
内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。
内部类就是外部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员(外部类不是内部类的友元)

class A 
{
public:
	class B 
	{
	private:
		int _b1;
	};
private:
	int _a1;
	int _a2;
};

小总结:

  • 内部类可以定义在外部类的public、protected、private都是可以的
  • 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象。
  • sizeof(外部类)=外部类,和内部类没有任何关系

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

(1) 方法1不使用内部类

class Sum
{
public:
    Sum()
    {
        _ret += _i;
        _i++;
    }
    static int get() //通过静态成员函数来返回私有静态成员变量
    {
        return _ret;
    }
private:
    //静态成员变量,类内部声明
    static int _ret;
    static int _i; 
};
//静态成员变量,类外部定义和初始化
int Sum::_ret = 0;
int Sum::_i = 1;

class Solution {
public:
    int Sum_Solution(int n) {
        Sum arr[n]; //数组构造n个Sum类型的对象
        return Sum::get();
    }
};

(2) 使用内部类

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::_ret = 0;
int Solution::_i = 1;

四、 匿名对象

在C++中,匿名对象是指没有显式名称的对象,通常用于临时存储数据或执行某些操作。这些对象在创建后会立即被初始化,并且通常只在当前的代码块或表达式中有效。

首先 一般情况下都是定义有名对象
例如:
在这里插入图片描述
那么 匿名对象,就没有上述的a1名字

	A(2);	//匿名对象
	A a1(1);//有名对象
	A(2);	//匿名对象,好处就是不用取名字,生命周期只有一行,运行完这一行就会自动调用析构函数
	//有名对象
	Solution s1;
	s1.Sum_Solution(10);
	
	//匿名对象
	Solution().Sum_Solution(1);

匿名对象,好处就是不用取名字,生命周期只有一行,运行完这一行就会自动调用析构函数

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

在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		cout << "A(const A& aa)" << endl;
	}
	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << 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()
{
	// 传值传参
	A aa1;
	f1(aa1);
	cout << endl;
	// 传值返回
	f2();
	cout << endl;
	// 隐式类型,连续构造+拷贝构造->优化为直接构造
	f1(1);
	// 一个表达式中,连续构造+拷贝构造->优化为一个构造
	f1(A(2));
	cout << endl;
	// 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
	A aa2 = f2();
	cout << endl;
	// 一个表达式中,连续拷贝构造+赋值重载->无法优化
	aa1 = f2();
	cout << endl;
	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

5G与智慧文旅的融合发展:推动旅游业转型升级与可持续发展

随着5G技术的飞速发展和广泛应用&#xff0c;其与智慧文旅的融合发展正成为推动旅游业转型升级与可持续发展的重要力量。5G技术以其高速率、低时延、大连接的特性&#xff0c;为智慧文旅注入了新的活力&#xff0c;助力旅游业实现更高效、更智能、更绿色的发展。本文将深入探讨…

力扣--从前序与中序遍历序列构造二叉树

题目&#xff1a; 思想&#xff1a; 首先先序遍历能确定根节点的值&#xff0c;此时查看该值在中序遍历中的位置&#xff08;如果索引为i&#xff09;&#xff0c;那么i左侧为左子树&#xff0c;i 右侧为右子树。从中序数组中即可看出左子树结点个数为 i&#xff0c;右子树节点…

Galxe:被低估的加密市场掘金地+Web3门户

在BTC ETF获得 SEC 的批准之后&#xff0c;机构资金大量买入推动BTC上涨&#xff0c;并带动整个加密市场回暖进入牛市。那么&#xff0c;对于习惯了熊市保守心态的投资者来说&#xff0c;接下来如何转换策略适应牛市&#xff1f;对即将进场的Web2用户来说&#xff0c;如何玩赚W…

Git小册-笔记迁移

Git简介 Git是目前世界上最先进的分布式版本控制系统&#xff08;没有之一&#xff09;。 所有的版本控制系统&#xff0c;其实只能跟踪文本文件的改动&#xff0c;比如TXT文件&#xff0c;网页&#xff0c;所有的程序代码等等&#xff0c;Git也不例外。版本控制系统可以告诉…

【elementplus】el-image图片预览的显示不全问题(和el-table、el-dialog组合使用时)

问题&#xff1a; 在和el-table、el-dialog组合使用时&#xff0c;el-image图片预览的时候&#xff0c;会可能出现显示不全图片的情况。 解决方法&#xff1a; <el-image-viewer:z-index"3000":teleported"true"/>element文档中有属性&#xff1a;…

vue2的element UI 表格单选

代码 this.$refs.multipleTable.toggleRowSelection(selection.shift(), false);multipleTable 是定义的表格的ref

【贪玩巴斯】关于在colab中上传本地csv使用方法(不用云)

有三种方法&#xff0c;但是这一种是最方便的。 当CSV文档在本地电脑&#xff0c;只需要输入以下代码&#xff08;个人建议在首行&#xff09;&#xff1a; from google colab import files uploadedfilesupload() 然后点击Choose Files 选择CSV文档&#xff08;注意文件是…

Spring框架Bean对象的五个作用域

一、前言&#xff1a;Bean对象简介 在Spring项目中&#xff0c;那些由Spring IoC容器所管理的对象&#xff0c;称为bean。简单地讲&#xff0c;bean就是由Spring容器初始化、装配及管理的对象&#xff0c;除此之外&#xff0c;bean就与应用程序中的其他对象没有什么区别了。 而…

phpstorm console xdebug

1.所有配置跟浏览器http请求一样 2.记得Current File 必须是controller文件 注意&#xff1a;如果没有出发断点&#xff0c;则echo phpinfo(),查看remote_port 和phpstorm 配置是否对上。

libevent源码解析:io事件(一)

文章目录 前言一、用例简单服务端实现参数设置 二、基本数据结构介绍三、源码分析event_base_newevent_newevent_addevent_base_dispatch 三、libevent和epoll中的事件标记epoll中的事件标记libevent中的事件标记libevent和epoll中事件标记的对应关系 总结 前言 libevent中对三…

sqlserver 默认端口号不通 1433 开启监听

1.打开SQL Server 2022 配置管理器 查看这3个东西是否启用&#xff0c;然后双击TCP/IP 把默认端口全部设置成1433 然后cmd netstat -an | find "1433" 查看端口是否打开监听

【AI视野·今日Sound 声学论文速览 第五十四期】Thu, 7 Mar 2024

AI视野今日CS.Sound 声学论文速览 Thu, 7 Mar 2024 Totally 8 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Sound Papers Can Audio Reveal Music Performance Difficulty? Insights from the Piano Syllabus Dataset Authors Pedro Ramoneda, Minhee Lee, Dasa…

感染了后缀为.[[backup@waifu.club]].wis勒索病毒如何应对?数据能够恢复吗?

引言&#xff1a; 在当今数字化时代&#xff0c;网络安全威胁层出不穷。其中&#xff0c;勒索软件是一种常见而具有破坏性的威胁之一。而.[[backupwaifu.club]].wis、[[MyFilewaifu.club]].wis、.[[Rastairmail.cc]].wis勒索病毒作为其中的一种&#xff0c;以其高度破坏性和隐…

软考69-上午题-【面向对象技术2-UML】-关系

一、关系 UML中有4种关系&#xff1a; 依赖&#xff1b;关联&#xff1b;泛化&#xff1b;实现。 1-1、依赖 行为&#xff08;参数&#xff09;&#xff0c;参数就是被依赖的事物&#xff0c;即&#xff1a;独立事物。 当独立事物发生变化时&#xff0c;依赖事务行为的语义也…

阿里云ECS磁盘扩容操作手册

云原生专栏大纲 文章目录 ESC磁盘扩容步骤前提条件云盘备份云盘扩容扩容分区和文件系统前提条件操作视频操作步骤准备工作&#xff1a;获取目标云盘信息步骤1&#xff1a;扩容分区步骤2&#xff1a;扩容文件系统 ESC磁盘扩容步骤 扩容已有云盘的操作步骤和注意事项_云服务器 …

一些硬件知识(六)

防反接设计&#xff1a; 同步电路和异步电路的区别: 同步电路:存储电路中所有触发器的时钟输入端都接同一个时钟脉冲源&#xff0c;因而所有触发器的状态的变化都与所加的时钟脉冲信号同步。 异步电路:电路没有统一的时钟&#xff0c;有些触发器的时钟输入端与时钟脉冲源相连…

微信小程序(五十三)修改用户头像与昵称

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.外界面个人资料基本模块 2.资料修改界面同步问题实现&#xff08;细节挺多&#xff0c;考虑了后期转服务器端的方便之处&#xff09; 源码&#xff1a; app.json {"window": {},"usingCompone…

打造经典游戏:HTML5与CSS3实现俄罗斯方块

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

Android随手记

activity的生命周期 创建时 onCreate() - onStart() - onResume() - onPause() - onStop() - onDestroy() 切换时 a切换到b a.onCreate() - a.onStart() - a.onResume - a.onPause - b.onCreate() - b.onStart() - b.onResume() - a.onStop() b切换回a b.onPause() - a.onR…

设计模式之——简单工厂模式

上图为简单工厂模式的架构图。 1&#xff0c;产品&#xff08;Product&#xff09; 将会对接口进行声明。 2&#xff0c;具体产品&#xff08;Concrete Products&#xff09;是产品接口的不同实现。 3&#xff0c;创建者&#xff08;Concrete Creators&#xff09;将会重写基…