C++:类和对象(下)

目录

1. 再谈构造函数

1.1构造函数题赋值

1.2初始化列表

初始化列表有什么用呢?

1.3 explicit关键字

2. Static成员

2.1概念

2.2特性

3. 友元

3.1友元函数

3.2友元类

4. 内部类(了解)

5.匿名对象

6.拷贝对象时的一些编译器优化


学习目标

  • 1. 再谈构造函数
  • 2. Static成员
  • 3. 友元
  • 4. 内部类
  • 5.匿名对象
  • 6.拷贝对象时的一些编译器优化

1. 再谈构造函数

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;
};

调用该函数后,对象中的成员变量会得到一个初始值,但是不能叫初始化

原因:初始化只能初始化一次,构造体函数内可以多次赋值(上述操作只能被成为赋值)

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成员变量
  • 自定义类型成员(且该类没有构造函数)


原因:

--引用的成员变量:引用必须在定义的时候初始化

--const成员变量:const必须在定义的时候初始化(只有一次机会)

--自定义类型成员(没有默认构造函数)必须初始化:和下面类似,若Date类里面有一个自定义类型的成员变量 Time _t ,若其没有适合的构造函数,会使得Date的实例化不了,所以其必须初始化

3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化

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

示例:

class A
{
public:
  A(int a)
   :_a1(a)
   ,_a2(_a1)
 {}
 
  void Print() {
    cout<<_a1<<" "<<_a2<

A. 输出1  1      B.程序崩溃     C.编译不通过     D.输出1  随机值

答案是D,类里先声明了_a2,先初始化_a2,_a2是用_a1初始化(_a1此时是随机值),

                                           然后初始化_a1, _a1用1初始化

所以最后,_a1 = 1, _a2  = 随机值;

初始化列表有什么用呢?

原来的构造函数是函数体内初始化,总会面临一些不好处理的情况(如下),因此引入初始化列表解决问题

--可以用来处理默认构造函数不适配的情况,当一个类(Time)给了一个不适配的构造函数,我们可以用初始化列表处理这个类

问题引入:现给一个Time类,提供一个带参数的构造函数,Date类里面包含Time类,我们该如何初始化Time类?

1.正常情况:Time类没有默认构造函数,编译器要处理自定义类型,会生成一个默认构造函数,但编译器又不会处理内置类型

示例:

class Time 
{
public:
            //不给构造函数
private:
	int _hour;
};

class Date
{
public:

private:
	int _year;
	Time _t;
};

int main() 
{
	Date d1;//Date和Time类都不给默认构造函数
	return 0;
}

这里看起来编译器什么都没做:

实际上--用Date去实例化d1,它的成员变量_year(不处理),

_t(自定义类型Time)--去调用它的默认构造函数,但Time没给默认构造函数,所以编译器自动生成一个默认构造函数,但其不会处理内置类型,Time的成员变量又是内置类型

所以看起来编译器什么都没做

C++:类和对象(下)_第1张图片

2.Time类给一个不适配的构造函数:

class Time 
{
public:
	Time(int hour) //给个不适配的构造函数
	{
		_hour = hour;
	}
private:
	int _hour;
};

class Date
{
public:

private:
	int _year;
	//Time _t;
};

int main() 
{
	Date d1;
	return 0;
}

C++:类和对象(下)_第2张图片

这里就会发现,我们处理不了Time类,并且实例化不了Date类

然后我们使用初始化列表解决:

class Time 
{
public:
	Time(int hour) //不适配的构造函数
	{
		_hour = hour;
	}
private:
	int _hour;
};

class Date
{
public:
    //要初始化_t只能通过初始化列表
	Date(int year, int hour)
		:_t(hour)//初始化列表解决自定义类型成员变量没有构造函数的情况
	{
		_year = year;
	}

private:
	int _year;
	Time _t;
};

int main() 
{
	Date d(2023,1);//给Date构造函数传值
	return 0;
}

这里就解决了,自定义类型的成员变量没有构造函数的情况

总结

1.自定义类型成员,推荐使用初始化列表初始化

2.初始化列表可以认为是成员变量定义的地方

1.3 explicit关键字

功能:explicit是阻止隐式类型转换的

隐式类型转换:

int i = 10;
double d = i;
//会产生临时变量tmp,把i转换为double类型后再拷贝给d

验证:

int i = 10;
double& d = i;
//这里引用是引用的临时变量,临时变量具有常性,不能引用

C++:类和对象(下)_第3张图片

这里加个const也行,因为现在是权限的平移(const 修饰的变量不能修改,常量也不能修改,加上const后再引用也不能修改)

有什么用呢?示例:

C++:类和对象(下)_第4张图片

现在加上const:

C++:类和对象(下)_第5张图片

这里做到的优化:可以不用构造一个string对象就能传参

也说明了:传参尽量用引用,用引用尽量加上const

传引用相比于传值调用:不用创建临时变量去拷贝,会更快一些

接下来看下面这段代码:

class Date
{
public:
	//构造函数
	Date(int year)
		:_year(year)
	{
		cout << "Date(int year)" << endl;
	}
	//拷贝构造
	Date(const Date& d) 
	{
		cout << "Date(const Date& d)" << endl;
	}

private:
	int _year;
};

int main() 
{
	Date d1(2023);//直接调用构造
	Date d2 = 2023;//构造 + 拷贝构造 + 优化  ==》 直接调用构造(隐式类型转换)

	return 0;
}

   1-- Date d1(2023);//直接调用构造
   2--Date d2 = 2023;//构造 + 拷贝构造 + 优化  ==》 直接调用构造(隐式类型转换)

   2中会先创建一个Date类型的临时变量tmp,然后把2023转换为Date类型拷贝构造给d2

现在在构造函数上为其加上  explicit关键字,就能阻止隐式类型的转换

class Date
{
public:
	//构造函数
	explicit Date(int year)//加上explicit
		:_year(year)
	{
		cout << "Date(int year)" << endl;
	}
	//拷贝构造
	Date(const Date& d) 
	{
		cout << "Date(const Date& d)" << endl;
	}

private:
	int _year;
};

int main() 
{
	Date d1(2023);//直接调用构造
	Date d2 = 2023;//构造 + 拷贝构造 + 优化  ==》 直接调用构造(隐式类型转换)

	return 0;
}

C++:类和对象(下)_第6张图片

2. Static成员

2.1概念

声明为static的类成员称为类的静态成员

用static修饰的成员变量,称之为静态成员变量;

用static修饰的成员函数,称之为静态成员函数。

注:静态成员变量一定要在类外进行初始化

问题1:实现一个类,来计算程序创建了多少个类对象

class A 
{
public:
	A() { ++_count; }//构造函数
	A(const A& a) { ++_count; }//拷贝构造函数
	~A() { --_count; }//析构函数

//private:
	static int _count;//声明
    int _a;
};

//类外定义初始化
int A::_count = 0;

2.2特性

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

静态成员为所有类对象所共享:

A a1;
A a2;
A a3;

这里使用A类实例化了3个对象,_count是静态成员变量,这3个类对象共享

                                                ,_a是每个对象独立有的

若static修饰的成员变量是私有的,我们该怎么访问呢?

--可以使用static修饰的成员函数来获取:

class A 
{
public:
	A() { ++_count; }//构造函数
	A(const A& a) { ++_count; }//拷贝构造函数
	~A() { --_count; }//析构函数
	//静态成员函数    ---   没有this指针
	static int GetCount() 
	{
		return _count;
	}

private:
	//静态成员变量,属于整个类,在静态区
	static int _count;//声明
	int _a;
};

//类外定义初始化
int A::_count = 0;

int main() 
{
	A a1;
	cout << A::GetCount() << endl;

	return 0;
}

问题:
1. 静态成员函数可以调用非静态成员函数吗?

--不能,静态成员函数没有隐藏的this指针,不能访问任何非静态成员
2. 非静态成员函数可以调用类的静态成员函数吗?

--可以,静态成员函数为所有类对象所共享

2.设计一个只能在栈上定义的对象的类

示例

class StackOnly 
{
public:
	static StackOnly CreateObj() 
	{
		StackOnly s;
		return s;
	}

private:
	StackOnly()//构造函数
		:_x(0)
		,_y(0)
	{}
private:
	int _x;
	int _y;
};


int main() 
{
	//显示定义的构造函数不加private,创建的对象可以在栈,静态区
	//StackOnly s1;		//栈上
	//static StackOnly s2;//静态区上
	
	//显示定义构造函数并加上private,让其只能通过调用成员函数来创建对象
	//要调用这个函数我们加上static即可
	StackOnly s = StackOnly::CreateObj();
	return 0;
}

3. 友元

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

        分类:友元函数和友元类

3.1友元函数

功能:友元函数可以直接访问类的私有成员

语法:它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字

示例:

class A 
{
	friend int sum(const A& a);
public:

private:
	int _a;
	int _b;
};

int sum(const A& a) 
{
	return a._a + a._b;
}

补充:

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

3.2友元类

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

  • 友元关系是单向的,不具有交换性。

如:Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接
访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。

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

4. 内部类(了解)

概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。


注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数

来访问外部类中的所有成员。但是外部类不是内部类的友元。

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

示列

class A
{
private:
static int k;
int h;
public:
class B // B天生就是A的友元
{
public:
void foo(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
};
};
int A::k = 1;
int main()
{
  A::B b;
  b.foo(A());
 
  return 0;
}

5.匿名对象

语法:

Date(2023);//匿名对象

特点:生命周期只有这一行

作用:当我们只想调用类里面的函数的时候,就可以使用匿名对象,而不用实例化对象

//使用匿名对象调用类里的成员函数
    Date().PrintYear(2023);

//实例化对象调用类里的成员函数
	Date d1(2023);
	d1.PrintYear(2023);

6.拷贝对象时的一些编译器优化

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

优化:连续一个表达式步骤中,连续的构造一般都会优化:例如构造+拷贝构造 --> 构造

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;
}

你可能感兴趣的:(C++,c++,开发语言)