C++——类和对象(下)

目录

(1)初始化列表

①概念

②语法

③初始化次数

④C++11标准

⑤定义时声明、初始化列表、函数体三者执行顺序

⑥必须写在初始化列表里的三种情况

⑦存在问题

⑧类属性的顺序问题

(2)什么时候写构造函数

二、static成员

(1)概念

(2)本质

(3)函数

三、友元

(1)概念

(2)友元函数

(3)友元类

①概念

②注意

(4)限定

(5)优缺点

四、内部类

(1)概念

(2)什么时候用内部类

五、匿名对象

(1)概念

(2)const引用修饰匿名对象

六、类型转换

(1)概念

(2)两种方式构造对象的区别

[①A a(10);](#①A a(10);)

[②A a = 10;](#②A a = 10;)

(3)隐式类型转化

①单参数的构造函数

②多参数的构造函数

③explicit

(4)两个类

七、拷贝优化


(1)初始化列表

①概念

在类中,语法上,我们把类的属性叫做声明,实现在类的构造函数中。在构造函数中我们可以通过函数体来实现类的属性,也可以通过初始化列表来实现类的属性。
初始化列表就是类的属性默认的值

②语法

cpp 复制代码
类名(参数列表)
	:属性1(表达式1)
	,属性2(表达式2)
	,  .........
{函数体 }
cpp 复制代码
#include<iostream>
using namespace std;
class Date {
public:
	Date(int year = 1946, int month = 2, int day = 14);
	void Print();
private:
	int _year;//声明部分
	int _month;
	int _day;
};
Date::Date(int year,int month,int day) 
	:_year(year)
	,_month(month)
	,_day(day)
{ }

③初始化次数

每个属性在初始化列表中只能出现一次。

④C++11标准

C++11以后,支持在类属性声明的位置给缺省值。

⑤定义时声明、初始化列表、函数体三者执行顺序

初始化列表(定义时赋值) > 函数体 > 定义时声明(给定默认值)

定义时声明只有一种情况才有用,就是我初始化列表和函数体里都没有初始化;如果初始化列表和函数体都对某一数据初始化了,最终的结果是函数体里的结果。

⑥必须写在初始化列表里的三种情况

const、引用、无默认构造的类(你已经手动写了带参的构造函数)对象初始化时只能写在初始化列表的位置。C++11以后可以写在定义的位置。

cpp 复制代码
class Data {
public:
	Data(int val,int& a);
private:
	const int _val;//被const修饰的属性必须写在初始化列表中
	int& _a;//引用初始化必须写在初始化列表中
};
Data::Data(int val, int& a)
	:_val(val)
	, _a(a)
{}
cpp 复制代码
class A {
public:
	A(int val) //写了带参的构造函数,这个类就是无默认构造的类,
		      //作为其它类属性时就只能写在初始化列表里
		:_val(val)
	{ }
private:
	int _val;
};
class B {
public:
	B()
		:a(20)
	{ }
private:
	A a;
};

⑦存在问题

无论你写不写初始化列表每个构造函数都默认有。

你不写,编译器会自动生成带初始化列表的构造函数。你写了构造函数但是没有写初始化列表,编译器会自动在你写的构造函数里插入隐式初始化列表(编译器自动生成、你看不见的初始化列表;内置类型是随机值,自定义类型会调用默认构造)

每一个类属性只能在初始化列表中存在一次。
不管你在初始化列表中对不对某个类属性进行初始化,都会走初始化列表。

⑧类属性的顺序问题

对类属性的初始化顺序只依照于定义时的顺序,与声明的顺序无关。

cpp 复制代码
class Date {
public:
	Date(int year, int month, int day);
private://定义
	int _year;
	int _month;
	int _day;
};
Date::Date(int year,int month,int day)//声明
	:_day(day)//按照定义时初始化的顺序进行初始化
	,_month(month)
	,_year(year)
{ }

练习:

cpp 复制代码
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();
}

打印的结果是1 随机值。

思路:先初始化_a2。此时_a1没有初始化就是随机值。再初始化_a1,此时_a1的值就是1。

(2)什么时候写构造函数

内置类型编译器默认生成的是随机值,需要我们写;自定义类型会自动处理,一般不需要我们写。

二、static成员

(1)概念

用static修饰的成员变量,称为静态成员变量。静态的类属性只能在类外初始化。

(2)本质

静态成员变量 = 类的全局变量

理解:

①存在于静态区。只初始化一次------不管类下有多少个对象,只初始化一次,被所有的对象共享。

②作用域就是类域,public修饰时可以在类外访问。

③生命周期贯穿整个程序。------存在于静态区,而静态区的生命周期就是贯穿整个程序。

(3)函数

静态成员函数没有this指针。

cpp 复制代码
class Data {
public:
	Data() 
		:_val(0)
	{}
	static void Print() {
		cout << _val << endl;//没有this指针无法访问
	}
private:
	int _val;
};

静态成员函数不能访问类的成员变量(没有this指针);可以访问其它静态成员变量(静态成员变量 = 类的全局变量)。

非静态的成员函数,可以访问类的任意成员变量。(有this指针)

(4)静态成员的访问

静态的成员变量和函数,可以通过作用域解析符(::)或成员访问运算符(.)访问。(::静态成员,.静态成员函数)

(5)与构造函数的关系

静态成员变量不走构造函数。(存在于静态区,与类没有关系,只是相当于类的全局变量)

三、友元

(1)概念

提供了一种突破类访问限定符的封装方式。(简单来说就是非类成员也可以访问private里的成员变量),分为友元函数和友元类。

(2)友元函数

友元函数不是类的成员函数,而是普通的函数(只是可以访问private的成员变量),也不存在this指针的概念。一个函数可以是多个类的友元函数。

使用友元函数,必须在想要访问的成员变量的类里面进行声明。(前面加上friend)

cpp 复制代码
class Data {
public:
	Data(int val)
		:_val(val)
	{ }
	friend void Print(Data& data);
private:
	int _val;
};
void Print(Data& data) {//friend声明后可以正常使用private
	cout << data._val << endl;
}

(3)友元类

①概念

整个类都被授权可以访问另一个类的private。(包括所有成员)

cpp 复制代码
class A {
public:
	A(int a):_a(a){}
	friend class B;//类B是A的友元
private:
	int _a;
};
class B {
public:
	B(int b) :_b(b) {}
	void Print(A& a) {//可以访问A的private
		cout << a._a << " " << _b << endl;
	}
private:
	int _b;
};
int main() {
	A a(1);
	B b(2);
	b.Print(a);
}

②注意

Ⅰ.友元类的关系是单向的。例如:B是A的友元,A不是B的友元,除非你声明两次。
Ⅱ.友元类的关系不能传递。例如:A是B的友元,B是C的友元。A和C没有关系。

(4)限定

友元函数和友元类都不受访问限定符的限定吗,可以在任意访问限定符的位置进行声明。

(5)优缺点

虽然提供了便利,但是破环了封装,增加了耦合性(模块之间的依赖程度)。

四、内部类

(1)概念

存在一个类内部的类。和外部类没有任何关系,只是受外部类类域(只能通过外部类访问内部类------外部类::内部类)和访问限定符(决定内部类是否能被外部访问)的限定。内部类默认为外部类的友元。

cpp 复制代码
class Outer {
public:
	class Inner {
	public:
		Inner(int val) :_val2(val) {}//内部类(存在public下,可以被外部看见)
		void Print(Outer& out) {//内部类默认是外部类的友元
			cout << out._val1 << endl << _val2 << endl;
		}
	private:
		int _val2;
	};
	Outer(int val):_val1(val){}
private:
	int _val1;
};
int main() {
	Outer out(10);
	Outer::Inner in(20);//内部类受外部类域的限定
	in.Print(out);
}

(2)什么时候用内部类

当B类的实现就是为了给A用就可以考虑将B类放到A类内部。放到public里说明想要让外部使用,放到private/protect里说明只想外部类使用内部类。

五、匿名对象

(1)概念

用类型(实参)定义出来的对象。只能用一次,生命周期仅存在于当前这一行。匿名对象就是临时对象/临时变量。

cpp 复制代码
class A {
public:
	A(int val):_val(val){}
	void Print() { 
		cout << _val << endl;
	}
private:
	int _val;
};
int main() {
	A(10).Print();//匿名对象的使用
}

(2)const引用修饰匿名对象

const引用修饰匿名对象,会将匿名对象的生命周期延长到const引用销毁。但是匿名对象为只读。

cpp 复制代码
class A {
public:
	A(int val):_val(val){}
	void Print() const{ 
		cout << _val << endl;
	}
private:
	int _val;
};
int main() {
	const A& val = A(10);
	val.Print();
}

六、类型转换

(1)概念

类型转换分为显示类型转换和隐式类型转换。显示类型就是前面加个括号,括号里是类型名。隐式类型转换就是编译器偷偷帮你转。

(2)两种方式构造对象的区别

①A a(10);

直接把10传给构造函数,不存在类型转化

②A a = 10;

"="两边的操作符类型不一致,所以会发生隐式类型转换。

转化过程:编译器看到的是A a = A(10)。会先调用构造函数先初始化这个匿名对象,接着调用拷贝构造函数将匿名对象拷贝给对象a,完成对a的构造。

(3)隐式类型转化

C++支持将内置类型隐式转化为类类型对象。但是必须存在相关内置类型的构造函数。

①单参数的构造函数

cpp 复制代码
class A {
public:
	A(int val):_val(val){}
	void Print() const{ 
		cout << _val << endl;
	}
private:
	int _val;
};
int main() {
	A a = 10;//进行了隐式类型转化(单参数的构造函数类型转化)
	a.Print();
}

②多参数的构造函数

C++11以后,支持多参数的隐式转化

cpp 复制代码
class Date {
public:
	Date(int year,int month,int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{ }
	void Print() {
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main() {
	Date date = { 2026,3,29 };//C++11以后支持多个参数的隐式类型转化
	date.Print();
}

③explicit

关键字之一。禁止隐式类型转化。

cpp 复制代码
class A {
public:
	explicit A(int val) :_val(val) {}//explicit修饰后,不再支持隐式类型转化
	void Print() const {
		cout << _val << endl;
	}
private:
	int _val;
};
int main() {
	A a = 10;//编译错误,构造函数已经被explicit修饰
}

(4)两个类

不同的类对象之间也可以隐式转化,需要相应的构造函数。

cpp 复制代码
class B;
class A {
public:
	A(int a) :_a(a) {}
	void Print() {
		cout << _a << endl;
	}
	friend B;
private:
	int _a;
};
class B {
public:
	B(A a) {//用A构造B
		_b = a._a;
	}
	void Print() {
		cout << _b << endl;
	}
private:
	int _b;
};
int main() {
	A a(10);
	a.Print();
	B b = a;
	b.Print();
}

七、拷贝优化

现代编译器为了提高效率,在不影响正确率的情况下。会减少一些传参和传返回值过程中的优化。优化过程完全取决于编译器,C++标准中没有明确的规定。

cpp 复制代码
class A {
public:
	A(int val)
		:_val(val)
	{ 
		cout << "构造完成" << endl;
	}
	A(const A& a)
	{
		_val = a._val;
		cout << "拷贝构造完成" << endl;
	}
private:
	int _val;
};
A test() {
	A a(10);
	return a;//返回时会创建一个临时对象,存储对象a;会调用拷贝构造
}
int main() {
	test();//只会打印构造完成,拷贝构造被优化掉了
}

相关推荐
骑龙赶鸭2 小时前
java开发项目中遇到的难点,面试!
java·开发语言·面试
张人玉2 小时前
C#通讯(上位机)常用知识点
开发语言·c#·通讯·上位机开发
NGC_66112 小时前
Java线程池七大核心参数介绍
java·开发语言
crescent_悦2 小时前
C++:Highest Price in Supply Chain
开发语言·c++
feng_you_ying_li2 小时前
底层实现map和set的第一步,AVL树的学习
c++
float_com2 小时前
【java进阶】------ Lambda表达式
java·开发语言
垫脚摸太阳2 小时前
第 36 场 蓝桥·算法挑战赛·百校联赛---赛后复盘
数据结构·c++·算法
码云数智-大飞2 小时前
Java接口与抽象类:从本质区别到架构选型
开发语言
小碗羊肉2 小时前
【从零开始学Java | 第二十三篇】泛型(Generics)
java·开发语言·新手入门