[C++]类和对象(下)

目录

一、再探构造函数

1.1初始化列表

1.2对象拷贝时编译器的优化

二、类型转换与匿名对象

2.1隐式类型转换

2.2匿名对象

三、友元与内部类

3.1友元

3.2内部类

四、static成员


一、再探构造函数

1.1初始化列表

初始化成员变量主要使用函数体内赋值,构造函数初始化还有⼀种方式,就是初始化列表,初始化列表的使用方式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"后面跟⼀个放在括号中的初始值或表达式。

演示代码:

cpp 复制代码
class qsy
{
public:
	//初始化列表来初始化
	qsy(int a = 0, int b = 0)
		:_a(a)
		, _b(b)
	{
	}
	void Print()const
	{
		cout << _a << ' ' << _b << endl;
	}
private:
	//变量声明 并没有开辟空间
	int _a = 1;
	int _b = 1;
};
int main()
{
	qsy q1;
	q1.Print();
	return 0;
}

特点:

• 每个成员变量在初始化列表中只能出现⼀次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地方。

• 引用成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进行初始化,否则会编译报错。

cpp 复制代码
class A
{
public:
	A(int a)
		:_a(a)
	{}
private:
	int _a=1;
};
class qsy
{
public:
	//初始化列表来初始化
	qsy(int a = 0, int b = 0)
		:_a(a)
		, _b(b)
		,_c(2) //初始化 const 成员变量
		,_d(_b)//初始化引用成员变量
		,_e(3) //没有默认构造的自定义类型
	{
	}
	void Print()const
	{
		cout << _a << ' ' << _b << ' ' <<_c<< ' '<< endl;
	}
private:
	//变量声明 并没有开辟空间
	int _a = 1;
	int _b = 1;
	//const 成员变量
	const int _c = 2;
	//引用成员变量
	int& _d = _a;
	//没有默认构造的自定义类型
	A _e;

};
int main()
{
	qsy q1;
	q1.Print();
	return 0;
}

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

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

总结:

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

1.2对象拷贝时编译器的优化

• 现代编译器会为了尽可提高程序的效率,在不影响正确性的情况下会尽可能减少⼀些传参和传参过程中可以省略的拷贝。

• 如何优化C++标准并没有严格规定,各个编译器会根据情况自行处理。当前主流的相对新⼀点的编译器对于连续⼀个表达式步骤中的连续拷贝会进行合并优化,有些更新更"激进"的编译还会进行跨行跨表达式的合并优化。

演示代码:

cpp 复制代码
class A
{
public:
	A(int a = 0)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}
	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
				_a1 = aa._a1;
		}
		return *this;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1 = 1;
};

void f1(A aa)
{}

A f2()
{
	A aa;
	return aa;//传值返回会生成临时对象
}


int main()
{
	A aa1 = 1; //临时对象构造+拷贝构造->优化为构造
	f1(aa1); //实参传给形参有一个拷贝,不会优化 减少拷贝可以用引用
	 
	f1(A(1)); //匿名对象 构造+拷贝构造->优化为直接构造

	f1(1);// 隐式类型转换 临时对象的构造+拷贝构造->优化为直接构造
	
	f2();//传值返回会生成临时对象,临时对象构造+拷贝构造->优化⼀个拷贝构造 (vs2019)
		//	 ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,直接变为构造。(vs2022)

	A ret = f2();// 构造aa 拷贝构造给ret 优化了中间的临时变量的拷贝构造 (vs2019)
	//	 ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,直接变为构造。(vs2022)

	A tmp;

	tmp = f2(); // 这里调用f2 的构造aa并不是=放在栈帧中 优化了一个构造
                //⼀个表达式中,连续拷贝构造 + 赋值重载->⽆法优化

}

二、类型转换与匿名对象

2.1隐式类型转换

C语言数据类型转换可以归纳为三种转换:自动转换,赋值转换,强制转换。

类型的自动转化

在进行计算时候,不同类型的数据要转换成同一类型,规则如下图所示:

转换方法:

float型数据自动转换为double型

char与short型自动转换为int型

int型与double型数据、直接将int转换为double

如此等等,简单说就是由低级向高级类型转换。可以直接转换,不能错误理解为int转换unsigned再转换为long最后转换为double。

类型的赋值转换

如果赋值运算符两侧类型不一样,但都是数值型或者字符型时,赋值时要进行类型抓换。

转换原则:

将整型数据赋给单、双精度变量时,数值不变,但以浮点数形式存储到变量中

将实型数据(包括单、双精度)赋给整型变量时,舍弃实数的小数部分。如x为整型变量,执行"x=4.25"时,取值为x=4。

同类型的短数据赋值给长变量,自动转换是正确的,例如,char和short型数据给int型变量赋值。

同类型的长数据给短变量赋值可能出错。例如,当unsigned int型的值超过了int变量的取值范围,赋值会出错

例如:unsigned int 变量给int变量赋值出错

简单说就是赋值运算符右边转换为左边的数据类型,高类型转换为低类型可能丢失数据。

强制转换

语法原则:

(类型名)(表达式)

例如:

cpp 复制代码
int a=7,b=2;
float y1,y2;
float y1=a/b;/*y1的值a/b为3.0*/
y2=(float)a/b;/*y2的值为3.5,float将a进行强制转换为浮点型,b也随之自动转换为浮点型*/

C++中不仅支持内置类型间的转换还支持内置类型隐式类型转换为类类型对象,但是需要有相关内置类型为参数的构造函数。

演示代码:

cpp 复制代码
class A
{
public:
	A(int a = 0)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1 = 1;
};

void f1(A aa)
{}

A f2()
{
	A aa;
	return aa;//传值返回会生成临时对象
}


int main()
{
	//平常构造
	A a1(1);
	//隐式类型转换构造:2创建一个A的临时对象 临时对象拷贝构造给a2
	A a2 = 2;
	
	//临时变量具有常性,不能直接将 3转换为 A的引用类型
	//A& ra2 = 3;

	//但是 const 原因就是创建的临时对象是常数性质
	const A& ra2 = 3;
}

多个参数也是可以的

cpp 复制代码
//C++11支持多参数
class A
{
public:
	A(int a = 0,int b=0)
		:_a1(a)
		,__a2(b)
	{
		cout << "A(int a)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1 = 1;
	int __a2 = 1;
};

void f1(A aa)
{}

A f2()
{
	A aa;
	return aa;//传值返回会生成临时对象
}


int main()
{
	//多参数需要{}来连接
	A a1 = { 2,2 };
	
	const A& a2 = { 3,3 };

}

如果不想发生类型转换 需用使用关键字 explicit 修饰构造函数

2.2匿名对象

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

匿名对象生命周期只在当前⼀行,⼀般临时定义⼀个对象当前用⼀下即可,就可以定义匿名对象。

演示代码:

cpp 复制代码
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(1);
	A aa2(2);

	return 0;
}

三、友元与内部类

3.1友元

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

• 外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数。

• 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。

• ⼀个函数可以是多个类的友元函数。

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

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

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

友元函数:

cpp 复制代码
// 前置声明,都则A的友元函数声明编译器不认识B
class B;

class A
{
	// 友元声明
	friend void func(const A& aa, const B& bb);
private:
	int _a1 = 1;
	int _a2 = 2;
};

class B
{
	// 友元声明
	friend void func(const A& aa, const B& bb);
private:
	int _b1 = 3;
	int _b2 = 4;
};
void func(const A& aa, const B& bb)
{
	cout << aa._a1 << endl;
	cout << bb._b1 << endl;
}

int main()
{
	A aa;
	B bb;
	func(aa, bb);
	return 0;
}

友元类:

cpp 复制代码
class A
{
	// 友元声明
	friend class B;
private:
	int _a = 1;
};
class B
{
public:
	void func1(const A& aa)
	{
		cout << aa._a << endl;
		cout << _b << endl;
	}
private:
	int _b = 3;

};
int main()
{
	A aa;
	B bb;
	bb.func1(aa);
	return 0;
}

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

3.2内部类

• 如果⼀个类定义在另⼀个类的内部,这个内部类就叫做内部类。内部类是⼀个独立的类,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。

• 内部类默认是外部类的友元类。

求1+2+3+...+n_牛客题霸_牛客网

这个例题便可以使用内部类很好地解决

演示代码:

cpp 复制代码
class Solution {
    class Sum
    {
    public:
        Sum()
        {
            ret+=i;
            ++i;
        }
    };

    static int i;
    static int ret;

public:
    int Sum_Solution(int n) {
        
         Sum *p =new Sum [n];
        delete [] p; 
        return ret;
    }

};

int Solution::i = 1;
int Solution::ret = 0;

class Sum这个内部类就是为了 Solution服务的

四、static成员

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

• 静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。

• 用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。

• 静态成员函数中可以访问其他的静态成员,但是不能访问非静态的,因为没有this指针。

• 非静态的成员函数,可以访问任意的静态成员变量和静态成员函数。

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

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

• 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是给构造函数初始化列表的,静态成员变量不属于某个对象,不走构造函数初始化列表。

演示代码:

cpp 复制代码
class A {
public:
	A()
	{
		++_x;
	}
	A(const A& a)
	{
		++_x;
	}
	~A()
	{
		--_x;
	}
	static int GetAcount()
	{
		//this->_a++; 不能访问 静态成员函数没有 this指针
		// 反过来 非静态的可以访问静态的

		return _x;
	}
private:
	// static 成员变量类里面声明且必须类外初始化
	//  存放在静态区
	//  static int _x = 0;
	  static int _x ;
	  int _a = 1;
};
//类外初始化
int A::_x = 0;
int main() 
{
	//如果是 public的话 可以访问 私有不行
	//cout << A::_x << endl;

	//静态成员变量 大小不算在类里面

	//cout << sizeof(A) << endl;//结果为1字节 仅仅表明对象存在

	cout << A::GetAcount() << endl;

	A a1, a2;

	cout << A::GetAcount() << endl;//计算现存的对象

	return 0;
}

**思考:<1>**假设已经有A,B,C,D 4个类的定义,程序中A,B,C,D构造函数调用顺序为?

**<2>**假设已经有A,B,C,D 4个类的定义,程序中A,B,C,D析构函数调⽤顺序为?

cpp 复制代码
C c;
int main()
{
 A a;
 B b;
 static D d;
 return 0;

}
相关推荐
AI街潜水的八角2 分钟前
基于C++的决策树C4.5机器学习算法(不调包)
c++·算法·决策树·机器学习
q5673152319 分钟前
在 Bash 中获取 Python 模块变量列
开发语言·python·bash
JSU_曾是此间年少31 分钟前
数据结构——线性表与链表
数据结构·c++·算法
许野平44 分钟前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
也无晴也无风雨1 小时前
在JS中, 0 == [0] 吗
开发语言·javascript
狂奔solar1 小时前
yelp数据集上识别潜在的热门商家
开发语言·python
此生只爱蛋1 小时前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
blammmp2 小时前
Java:数据结构-枚举
java·开发语言·数据结构
何曾参静谧2 小时前
「C/C++」C/C++ 指针篇 之 指针运算
c语言·开发语言·c++
暗黑起源喵2 小时前
设计模式-工厂设计模式
java·开发语言·设计模式