类和对象----最终篇

个人主页:小则又沐风

个人专栏:<数据结构>

<竞赛专栏>

<C语言>
<C++>

座右铭

路虽远,行则将至;事虽难,做则必成

目录

前言:

[一 再探构造函数](#一 再探构造函数)

[二 类型转化](#二 类型转化)

[三 static成员](#三 static成员)

[四 友元](#四 友元)

[五 内部类](#五 内部类)

[六 匿名对象](#六 匿名对象)

[七 总结](#七 总结)


前言:

在前面我们介绍了类和对象的基础的语法知识,我们初步了解了类的构造函数,析构函数,和函数的重载等等,今天我们来对类和对象收 个尾.

一 再探构造函数

  • 之前我们在初始化成员变量的时候我们是在构造函数的体内来进行初始化的,其实还有一种初始化的方式,就是通过我们的初始化列表.
  • 初始化列表的使⽤⽅式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成 员列表,每个"成员变量"后⾯跟⼀个放在括号中的初始值或表达式
cpp 复制代码
class date
{
public:
	date()
		:_year(2026)
		, _month(4)
		, _day(4)
	{
	}
	void print()
	{
		cout << _year << ' ' << _month << ' ' << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
  • 每个成员变量在初始化列表中只能出现⼀次,语法理解上初始化列表可以认为是每个成员变量定义 初始化的地⽅

那么这时候就有人发问了,既然初始化列表和构造函数可以做到对我们的成员变量的初始化,那么我们为什么非要写这个初始化列表呢?我们知道我们的const的变量只能在定义的时候初始化.

我们来看这一串代码;

cpp 复制代码
class date
{
public:
	date()
		:_year(2026)
		, _month(4)
		, _day(4)
	{
		_h = 10;
	}
	void print()
	{
		cout << _year << ' ' << _month << ' ' << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	const int _h;
};

我们可以看到我们的报错是我们把不能修改的变量尝试了修改.那么我们想在我们的构造函数体内对我们的const变量进行初始化,那么还有什么情况吗?

cpp 复制代码
class date
{
public:
	date()
		:_year(2026)
		, _month(4)
		, _day(4)
	{
		_h = 10;
		_min = _day;
	}
	void print()
	{
		cout << _year << ' ' << _month << ' ' << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	const int _h;
	int& _min;
};

我们可以看到我们的引用的类型也是这样的,所以我们这时候就必须使用我们的初始化列表了.

cpp 复制代码
class date
{
public:
	date()
		:_year(2026)
		, _month(4)
		, _day(4)
		, _h (4)
        ,_min(_day)
	{
	}
	void print()
	{
		cout << _year << ' ' << _month << ' ' << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	const int _h;
	int& _min;
};

在这里的引用变量没有实际的含义,只是作为讲解.

  • 引⽤成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进⾏初始 化,否则会编译报错
  • C++11⽀持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显⽰在初始化列表初始化的 成员使⽤的
cpp 复制代码
class date
{
public:
	date()
		/*:_year()
		, _month()*/
		 :_day(5)
		/*, _h (4)
        ,_min(_day)*/
	{
	}
	void print()
	{
		cout << _year << ' ' << _month << ' ' << _day << endl;
	}
private:
	int _year=2026;
	int _month=4;
	int _day=4;
	/*const int _h;
	int& _min;*/
};

其实我们在调用构造函数的时候我们都会调用我们的初始化列表,因为他是我们的成员变量定义的地方,如果我们在初始化的列表显示化进行了初始化那么我们的行为就好似int x=10;这样一般定义加初始化,如果没有的话,我们也会调用初始化列表只不过我们的初始化的值是随机值而已.我们会在我们的构造函数的函数体内进行赋值.如果我们在声明的时候给了缺省值,那么我们如果没有显示初始化变量的话我们的变量就会是我们的缺省值.

是不是这样就明白为什么我们编译器给的默认构造是随机值了吧

  • 尽量使⽤初始化列表初始化,因为那些你不在初始化列表初始化的成员也会⾛初始化列表,如果这 个成员在声明位置给了缺省值,初始化列表会⽤这个缺省值初始化。如果你没有给缺省值,对于没 有显⽰在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有 显⽰在初始化列表初始化的⾃定义类型成员会调⽤这个成员类型的默认构造函数,如果没有默认构 造会编译错误

我们来看这一张图片:

但是我们需要注意的一点是我们初始化列表的顺序:

我们来看这一串代码,

cpp 复制代码
#include<iostream>
using namespace std;
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();
}

我们来思考一下这一串代码的运行结果是什么?

按照我们常规的想法,我们先对a1进行初始化,所以我们的a1就是我们的1'然后我们的a2就是用a1的值进行初始化,所以我们判断这一串代码的运行结果是1 1

但是事实真的是这样吗?

我们来看运行结果:

为什么我们的a2是一串垃圾值呢?这时候我们就需要知道我们的初始化列表的初始化的顺序了.

我们初始化列表初始化的顺序是按照我们声明的顺序进行初始化的,也就是说我们这串代码的运行的逻辑是,我们先对a2进行初始化但是这时候我们的a1并没有初始化,所以我们的a2的初始值就是我们的编译器所决定的值,在我的环境下我的a2是一个随机值,然后我们在对a1初始化,所以这串代码的运行结果是这样的

二 类型转化

我们来看这一串的代码:

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	A(int a = 1)
	{
		_a = a;
	}
	void print()
	{
		cout << _a << endl;
	}
private:
	int _a;
};
int main()
{
	A a = 2;
	a.print();
	return 0;
}

我们来看我们是怎么初始化我们的对象的,我们居然使用一个int类型的内置类型来初始化一个我们的自定义类型,这样是可以的吗???

不妨我们来试试

我们来看并没有报错,我们来看运行结果是什么样子:

我们居然发现这样真的可以,那是为什么呢?明明我们之前学习的这个样子的是我们的拷贝构造但是这是什么东西.

这其实就是我们的类型的转化,我们的内置类型会根据构造出一个我们的类的临时对象然后拷贝给我们的对象.

  • C++⽀持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数
cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	A(int a = 1)
	{
		_a = a;
	}
	void print()
	{
		cout << _a << endl;
	}
private:
	int _a;
};
int main()
{
	A a = 2;
	a.print();
	A& a2 = 3;
	a2.print();
	return 0;
}

如果我们这样写就会报错了:

这里需要解释的是我们因为我们创建出了临时对象,但是呢,我们的临时对象是具有常性的,所以我们这样的行为相当于我们对一个变量权限的放大,所以是不被允许的,所以我们需要加上const的修饰

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	A(int a = 1)
	{
		_a = a;
	}
	void print() const 
	{
		cout << _a << endl;
	}
private:
	int _a;
};
int main()
{
	A a = 2;
	a.print();
	const A& a2 = 3;
	a2.print();
	return 0;
}

这样就行了.

三 static成员

  • ⽤static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进⾏初始化
  • 静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区
  • ⽤static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针
  • 因为static修饰的函数是没有this指针的所以我们的这个函数是无法访问我们的普通的成员变量的只能访问我们的静态成员变量.
cpp 复制代码
class time
{
public:
	time()
	{
		_day++;
	}
	~time()
	{
		_day++;
	}
	static int getday()
	{
		return _day;
	}


private:
	int _h;
	int _min;
	int _s;
	static int _day;
};

上面是包含静态成员变量和静态成员函数的类的定义示例,下面我们来看这些有什么特别的:

cpp 复制代码
#include<iostream>
using namespace std;
class Time
{
public:
	Time()
	{
		_day++;
		cout << "Time()" << endl;
	}
	~Time()
	{
		_day++;
		cout << "~Time()" << endl;
	}
	static int getday()
	{
		return _day;
	}


private:
	int _h;
	int _min;
	int _s;
	static int _day;
};
int Time::_day = 4;
int main()
{
	Time t1;
	Time t2;
	cout << Time::getday() << endl;

	return 0;
}

因为我们的静态成员是不属于某个具体的对象,不存在对象中,存放在静态区,所以我们这一串代码的运行结果是6

因为类的所有对象是公用一个的,还需要注意的是我是怎么初始化这个静态变量的.

下面我们来看这样的代码:

cpp 复制代码
cout << Time::_day << endl;

如果我们这么访问的话是被允许的吗??

不妨我们编译一下看看

很明显是不可以的,所以我们的静态成员还是受我们的限定词的限定的.

下面我们来验证静态成员函数是否真的不含有我们的this指针.

cpp 复制代码
static int getday()
{
	_h += 1;
	return _day;
}

我们这样写的话我们的编译会通过吗???

是不行的,所以我们的静态成员函数是不包含我们的this指针的.


  • ⾮静态的成员函数,可以访问任意的静态成员变量和静态成员函数
  • 突破类域就可以访问静态成员,可以通过类名::静态成员或者对象.静态成员来访问静态成员变量 和静态成员函数
  • 静态成员也是类的成员,受public、protected、private访问限定符的限制
  • 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员 变量不属于某个对象,不⾛构造函数初始化列表

四 友元

在之前的类和对象的讲解的时候我们简单了解了友元函数的相关的知识,下面我们来详细讲解一下友元这一语法

  • 友元提供了⼀种突破类访问限定符封装的⽅式,友元分为:友元函数和友元类,在函数声明或者类 声明的前⾯加friend,并且把友元声明放到⼀个类的⾥⾯

也就是我们所说的加了友元声明的我就是你的好朋友,我就能访问你的私有的成员了

  • 外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数
  • 友元函数可以在类定义的任何地⽅声明,不受类访问限定符限制
  • ⼀个函数可以是多个类的友元函数
  • 友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员
  • 友元类的关系是单向的,不具有交换性,⽐如A类是B类的友元,但是B类不是A类的友元

这样的话我们的A就可以访问B的私有和保护成员但是B却不可以,.(是不是有一种背刺的感觉)

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

只有我们声明了谁和谁是友元,他们才是

  • 有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多⽤

在之前的讲解中我们认识了友元函数,所以我下面会对友元类进行详细的讲解:

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	friend class B;
	/*void funa(const B&b)
	{
		cout << b._b << endl;
	}*/
private:
	int _a=1;
};
class B
{
public:
	void funb(const A&a)
	{
		cout << a._a << endl;
	}
private:
	int _b;
};
int main()
{
	A a;
	B b;
	b.funb(a);
	/*a.funa(b);*/
	return 0;
}

在这里我们把A设置成我们B类的友元所以我们的B是可以访问A的私有的,但是我们这时候如果要在A中访问我们的B的私有呢?

很明显我们的A是不能访问B的私有的

所以友元是单向的.

五 内部类

内部类人如其名,就是一个类的定义是在我们另一个类的内部的,那么这时候我们的内部类默认是我们外部类的友元类

  • 如果⼀个类定义在另⼀个类的内部,这个内部类就叫做内部类。内部类是⼀个独⽴的类,跟定义在 全局相⽐,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类
  • 内部类本质也是⼀种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使⽤,那么可以考 虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其 他地⽅都⽤不了

总而言之:就是我们的内部类是可以访问我们的外部类的所有的成员,但是外部类不能访问内部类的私有:

下面我们来看这一个代码:

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	class B
	{
	public:
		void funb(const A&a)
		{
			cout << _k << endl;
			cout << a._a << endl;
		}
	private:
		int _b;
 };
private:
	int _a;
	static int _k;
};
int A:: _k = 1;
int main()
{
	A a;
	A::B b;
	b.funb(a);
	return 0;
}

在这里B是A的内部类所以B是可以访问A类的所有的成员的

但是如果我们尝试A访问B的私有的话会发生什么:

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	class B
	{
	public:
		void funb(const A&a)
		{
			cout << _k << endl;
			cout << a._a << endl;
		}
	private:
		int _b;
 };
	void funa(const B& b)
	{
		cout << b._b << endl;
	}
private:
	int _a;
	static int _k;
};
int A:: _k = 1;
int main()
{
	A a;
	A::B b;
	b.funb(a);
	return 0;
}

显然这样是不对的

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
//public:
	class B
	{
	public:
		void funb(const A&a)
		{
			cout << _k << endl;
			cout << a._a << endl;
		}
	private:
		int _b;
 };
	/*void funa(const B& b)
	{
		cout << b._b << endl;
	}*/
private:
	int _a;
	static int _k;
};
int A:: _k = 1;
int main()
{
	A a;
	A::B b;
	b.funb(a);
	return 0;
}

可以看出如果我们把内部类设置成私有的那么我们无论怎么都是无法访问到我们的内部类的

所以我们这样的情况下我们的内部类就是我们的外部类的专属

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	class B
	{
	public:
		void funb(const A&a)
		{
			cout << _k << endl;
			cout << a._a << endl;
		}
	private:
		int _b;
 };
	/*void funa(const B& b)
	{
		cout << b._b << endl;
	}*/
private:
	int _a=2;
	static int _k;
};
int A:: _k = 1;
int main()
{
	cout << sizeof(A) << endl;
	A a;
	A::B b;
	b.funb(a);
	return 0;
}

我们来看一下内部类是否在外部类中定义,我们来求一下外部类的大小:

可以看出来外部类的大小是4

显而易见内部类的定义并不是在我们的外部类

六 匿名对象

⽤类型(实参)定义出来的对象叫做匿名对象,相⽐之前我们定义的类型对象名(实参)定义出来的 叫有名对象

匿名对象⽣命周期只在当前⼀⾏,⼀般临时定义⼀个对象当前⽤⼀下即可,就可以定义匿名对象

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
private:
	int _a=1;
};
int main()
{
	A();
	return 0;
}

这样A();就是使用上了匿名对象

对于匿名对象的使用我们在之后的讲解会进一步的了解

七 总结

本次内容围绕 C++ 类与对象核心知识点展开。再探构造函数,明晰其初始化与对象创建的作用;掌握类型转换规则,理解隐式与显式转换的差异与应用。static 成员为类所有,实现数据共享与独立访问,突破对象限制。友元打破封装,让外部函数或类可访问类私有成员,需谨慎使用。内部类可直接访问外部类私有成员,私有内部类能有效隐藏实现细节,强化封装。匿名对象生命周期仅一行,简化临时操作,提升代码简洁度.

今天的内容,类和对象的知识就结束了,希望我写的东西能够帮助到大家.

谢谢大家的观看!!!

相关推荐
喵叔哟2 小时前
4.【.NET10 实战--孢子记账--产品智能化】--C# 14 新语法特性详解与实战应用
java·c#·.net
travel_wsy2 小时前
PLY三维模型在vue中的展示
前端·javascript·vue.js
liliangcsdn2 小时前
LLM如何以ReAct Agent方式统计分析去重后数据
数据库·人工智能·全文检索
还是大剑师兰特2 小时前
Vite + Vue 3 一体化开发调试插件:vite-plugin-vue-devtools
前端·javascript·vue.js
晓得迷路了2 小时前
栗子前端技术周刊第 123 期 - axios 包遭入侵、Babylon.js 9.0、Node.js 25.9.0...
前端·javascript·axios
问道飞鱼2 小时前
【数据库相关】MySQL全分类SQL详解(超多数据类型+全约束+实战落地)
数据库·sql·mysql·范例
不剪发的Tony老师2 小时前
mayfly-go:一款基于WEB的服务器、数据库、中间件统一运维平台
运维·服务器·数据库
Cobyte2 小时前
如何使用飞书机器人连接本地 AI Agent
前端·aigc·ai编程
lifallen2 小时前
Flink Agent:ActionTask 与可续跑状态机 (Coroutine/Continuation)
java·大数据·人工智能·语言模型·flink