【C++】类和对象(下)—— 再谈构造函数 + static成员 + C++11初始化补丁 + 友元

目录

  • 1. 再谈构造函数
    • 1.1 构造函数内赋值
    • 1.2 初始化列表
    • 1.3 explicit关键字
  • 2. static成员
    • 2.1 静态成员变量
    • 2.2 静态成员函数
  • 3. C++11 的成员初始化新玩法
  • 4. 友元
    • 4.1 友元函数
    • 4.2 友元类
  • 5. 内部类

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

int main()
{
	Date d1(2022, 2, 22);
	return 0;
}

但是对于像const成员变量,必须在定义的时候同时初始化。如果在函数体内初始化就会报错 ——

【C++】类和对象(下)—— 再谈构造函数 + static成员 + C++11初始化补丁 + 友元_第1张图片

为此我们引入了初始化列表,就是给成员变量找到一个依次定义处理的地方。

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

int main()
{
	Date d1(2022, 2, 22); //实例化/定义一个对象
	return 0;
}

注:每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次),可以不出现(不是每个都必须在这儿初始化)

❤️ 1. 类中包含以下成员,必须放在初始化列表进行初始化。注意,**初始化列表就是成员变量定义的地方!!**这是理解这里的关键。

  • const成员变量

    因为const成员变量必须在定义的时候同时初始化

  • 引用成员

    因为引用成员变量必须在定义的时候同时初始化

    代码示例 ——

    class Date
    {
    public:
    	Date(int year, int month, int day,int i)
    		: _N(10)
    		, _ref(i)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    
    private:
    	int _year; //声明
    	int _month;
    	int _day;
    
    	const int _N; //const
    	int& _ref;	  //引用
    };
    
    int main()
    {
    	int i = 0;
    	Date d1(2022, 2, 22, i); //实例化/定义一个对象
    	return 0;
    }
    
  • 没有默认构造函数自定义类型成员

    回忆:默认的构造函数,即不用传参的有三个 ——

    1. 我们不写编译器自己生成的
    2. 无参的
    3. 全缺省的

    没有默认构造函数(什么情况下没有?我们自己写了一个构造函数,还是带参的,编译器不再自动生成),编译器调不动,需要在定义的时候自己显式的传参去调。示例如下 ——

    class A
    {
    public:
    	A(int a)
    	{
    		_a = a;
    	}
    private:
    	int _a;
    };
    
    class Date
    {
    public:
    	Date(int year, int month, int day,int i)
    		: _N(10)
    		, _ref(i)
    		, _aa(1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    
    private:
    	int _year; //声明
    	int _month;
    	int _day;
    
    	const int _N;
    	int& _ref;
    	A _aa; //没有默认构造函数的自定义类型成员
    };
    
    int main()
    {
    	int i = 0;
    	Date d1(2022, 2, 22, i); //实例化/定义一个对象
    	return 0;
    }
    

其它成员变量,如int _year等在哪里初始化都可以。

❤️ 2. 建议尽量使用初始化列表,对于自定义成员变量,初始化列表可以提高效率。

对比 —— 为了观察调用情况,在成员函数内部打印

  • 不使用初始化列表

【C++】类和对象(下)—— 再谈构造函数 + static成员 + C++11初始化补丁 + 友元_第2张图片

  • 使用初始化列表

【C++】类和对象(下)—— 再谈构造函数 + static成员 + C++11初始化补丁 + 友元_第3张图片

总结:内置类型成员,在函数体和在初始化列表初始化都可以;自定义类型的成员,建议在初始化列表初始化,这样更高效。

代码如下 ——

#include
using namespace std;

class A
{
public:
	// 构造函数 - 全缺省
	A(int a = 0)
	{
		cout << "A(int a = 0)" << endl;
	}
	// 拷贝构造
	A(const A& aa)
	{
		cout << "A(const A& aa)" << endl;
		_a = aa._a;
	}
	// 赋值重载
	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& a)" << endl;
		_a = aa._a;
		return *this;
	}
private:
	int _a;
};

class Date
{
public:
	 不使用初始化列表
	//Date(int year, int month, int day, const A& aa)
	//{
	//	_aa = aa;
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}

	//使用初始化列表
	Date(int year, int month, int day, const A& aa)
		: _aa(aa)
	{
		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year; //声明
	int _month;
	int _day;

	A _aa;
};

int main()
{
	A aa(10);
	Date d1(2022, 2, 22, aa); //实例化/定义一个对象
    
     可以使用匿名对象,一行解决
    //Date d1(2022, 2, 22, A(10));
	return 0;
}

❤️ 3. 初始化列表中的初始化顺序是在类中的声明次序,与其在初始化列表中的先后次序无关、

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

class A {
public:
	A(int a)
		:_a1(a)		//2.
		, _a2(_a1) 	//1.
	{}

	void Print() {
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2; //初始化顺序 - 声明顺序
	int _a1;
}

int main() {
	A aa(1);
	aa.Print();
}

选dog。运行结果 ——

【C++】类和对象(下)—— 再谈构造函数 + static成员 + C++11初始化补丁 + 友元_第4张图片

建议一个类的声明顺序和初始化列表的出现顺序保持一致,这样就不容易出问题。

1.3 explicit关键字

阅读如下代码,为什么一个整型能转换为日期类?为了后续分析,我们还是在成员函数中打印。

#include
using namespace std;

class Date
{
public:
	Date(int year)
		:_year(year)
	{
		cout << "Date(int year)" << endl;
	}

	Date(const Date& dd)
	{
		_year = dd._year;
		cout << "Date(const Date& year)" << endl;
	}
private:
	int _year;

};

int main()
{
	Date d1(2002);
	Date d2 = 2022;//思考?
	return 0;
}

为什么一个整型能转换为日期类?这其实是因为单参数的构造函数中发生了隐式类型转换

回忆C语言中隐式类型转换

	// 隐式类型转换 - 相近类型 -- 表示意义相似的类型
	double d = 1.1;
	int i = d;
	const int& i = d; //后文马上解释

	// 强制类型转换 - 无关类型
	int* p = &i;
	int j = (int)p;

回忆在C++入门讲常引用时,讲到隐式类型转换时会产生临时变量i其实是临时变量的引用,这里也类似。(临时变量具有常属性,不可修改,因此要加上const)

【C++】类和对象(下)—— 再谈构造函数 + static成员 + C++11初始化补丁 + 友元_第5张图片

这儿本来是用2022构造一个临时对象Date(2022),再用这个对象拷贝构造d2。但是C++在连续的过程中,编译器会优化多个构造,合二为一,因此这里被优化为直接就是一个构造【C++】类和对象(下)—— 再谈构造函数 + static成员 + C++11初始化补丁 + 友元_第6张图片

相当于这两句代码 ——

Date tmp(2022); //先构造
Date d2(tmp);	//再拷贝构造

由于这个单参数的构造函数,整形就可以构造一个日期类的对象。

上述代码可读性不是很好,用explicit修饰构造函数,将会禁止单参构造函数的隐式转换 ——

【C++】类和对象(下)—— 再谈构造函数 + static成员 + C++11初始化补丁 + 友元_第7张图片

2. static成员

2.1 静态成员变量

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

【面试题】实现一个类,计算中程序中创建出了多少个类对象。这并不好数,因为编译器可能有优化。

下面给出整个认知过程 ——

由于要对同一个变量进行++,比较朴素的想法是定义一个全局count,但是这是有问题的,如果如果有两个类共用一个count会有累加效应,且别人可以在外部随意更改。

#include
using std::cout;
using std::endl;

int count = 0;

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		count++;
	}
	A(const A& aa)
	{
		count++;
	}
private:
	int _a;
};

void f(A a)
{

}

int main()
{
	A a1;
	A a2 = 1;
	f(a1);
	cout << count << endl;
	count++; //存在问题:别人可以随意更改
	cout << count << endl;
	return 0;
}

如何把count与这个类深度绑定呢?改成成员变量吗?不可以,这样每一个对象都各自有一个count,不是对同一个变量++。这时我们要引入静态成员变量。

❤️ 1. 静态成员属于整个类,为所有类对象所共享,不属于某个具体的实例,生命周期为整个工程

❤️ 2. 静态成员变量必须在类外的全局定义,定义时不添加static关键字。注意:这是一个特例,只有这里能在类外访问私有。不然你想想,怎么对这个静态成员变量定义?

作为私有成员,怎么样在类外访问呢?只能提供一个公用的成员函数getCount。代码如下 ——

我们可以通过对象来访问成员函数getCount

#include
using namespace std;

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		_scount++;
	}
    
	A(const A& aa)
		: _a(aa._a)
	{
		_scount++;
	}
    
	int getCount()
	{
		return _scount;
	}
private:
	int _a;
	static int _scount; //静态成员变量
};

int A::_scount = 0;	//静态成员变量必须在类外的全局定义

void f(A a)
{

}

int main()
{
	A a1;
	A a2 = 1;
	f(a1);
	cout << a1.getCount() << endl; //通过对象来访问成员函数
	return 0;
}

那么有没有更好的方式,不需要定义对象就可以获取到呢?这要引入我们的静态成员函数。

2.2 静态成员函数

❤️ 静态成员函数没有隐藏的this指针只能访问静态成员变量和函数。不能访问任何非静态成员,这很合乎情理,你都没this指针

#include
using namespace std;

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		_scount++;
	}
	A(const A& aa)
		: _a(aa._a)
	{
		_scount++;
	}
    // 静态成员函数
	static int getCount()
	{
		return _scount;
	}
private:
	int _a;
	static int _scount;
};

int A::_scount = 0;

void f(A a)
{

}

int main()
{
	A a1;
	A a2 = 1;
	f(a1);
	cout << A::getCount() << endl; //可以通过类域来找
	cout << a1.getCount() << endl; //当然了,通过对象来找
	return 0;
}

静态成员函数,可以通过类域访问。当然通过对象来找也可以,但这并不意味着在对象里面找,它也不存在于对象里。

注:含有静态成员变量的类,一般含有一个静态成员函数,用于访问静态成员变量。

3. C++11 的成员初始化新玩法

这其实是C++11打的一个补丁。众所周知,默认生成的构造函数,对内置类型不处理就是一个随机值,对自定义类型会调用它的构造函数处理。这有些不太合理。

于是C++11打了一个补丁,非静态成员变量,可以在函数声明给缺省值。若传参,就用传的参数对成员变量进行初始化;若没有传参,则用给定缺省值进行初始化。

#include
using namespace std;

class B 
{
public:
	B(int b = 0)
		:_b(b)
	{}
	int _b;
};

class A 
{
public:
	void Print()
	{
		cout << a << endl;
	}
private:
	// 非静态成员变量,可以在成员声明时给缺省值。
	int a = 10;
};


int main()
{
	A a;
	a.Print();

	//A().Print(); //匿名对象调用也可

	return 0;
}

❤️ 注意:这不是初始化,因为这是声明,不能初始化,我对你哪个对象初始化?

且缺省值是比较宽泛的 ——

#include
using namespace std;

class B 
{
public:
	B(int b = 0)
		:_b(b)
	{}
	int _b;
};

class A 
{
public:
	void Print()
	{
		cout << a << endl;
		cout << b._b << endl;
		cout << p << endl;
	}
private:
	// 非静态成员变量,可以在成员声明时给缺省值。
	int a = 10;
	B b = 20; // 因为单参数的构造函数,等价于B b = B(20);
	int *p = (int*)malloc(4);
	int arr[10] = { 1, 2, 3, 4, 5 }; //vs2019支持,2013不支持
	static int n;
};

int A::n = 10;

int main()
{
	A a;
	a.Print();

	//A().Print(); // 匿名对象调用也可

	return 0;
}

4. 友元

友元提供了一种突破封装的方式,有时提供了便利。但是友元就像黄牛一样,破坏管理规则,会增加耦合度,破坏了封装,所以友元不宜多用

查阅 cplusplus.com - The C++ Resources Network 可知,coutcin对于内置类型之所以能“自动识别类型”,是因为库里面已经把函数重载都写好了。

【C++】类和对象(下)—— 再谈构造函数 + static成员 + C++11初始化补丁 + 友元_第8张图片

【C++】类和对象(下)—— 再谈构造函数 + static成员 + C++11初始化补丁 + 友元_第9张图片

那对于Date类这样的自定义类型,怎么样内置类型一样,直接使用流提取、流插入打印呢?

	Date d1(2022, 2, 23); //诶!刚好是19岁的最后一天
	d1.PrintWeekday();
	cout << d1; //like this?
	cin >> d2;	//like this?

现在我们尝试去重载operator<<,失败了,根本调不动这个函数。

【C++】类和对象(下)—— 再谈构造函数 + static成员 + C++11初始化补丁 + 友元_第10张图片

这是因为在运算符重载中,如果是双操作数的运算符重载,第一个参数也就是左操作数,第二个参数是右操作数。cout这个输出流对象和隐含的this指针在抢占第一个参数的位置。如果像下面这样,是能调起来,就是流倒灌了,您总不能这么用吧

【C++】类和对象(下)—— 再谈构造函数 + static成员 + C++11初始化补丁 + 友元_第11张图片

于是把它挪到类外,将operator<<重载成全局函数,摆脱隐藏的this指针约束,但是又有类外无法访问成员的问题

注:这里为了支持连续输出,重载函数需要有返回值ostream&,原理类似于连续赋值,只不过cout的结合性是从左至右

【C++】类和对象(下)—— 再谈构造函数 + static成员 + C++11初始化补丁 + 友元_第12张图片

那么这里就需要引入友元来解决。

4.1 友元函数

友元函数可以直接访问类的私有/保护成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

注:

  • 友元函数想访问类的私有和保护成员,但不是类的成员函数
  • 友元函数不能用const修饰(const修饰的是非静态成员函数,修饰的是tihs所指向的对象)
  • 友元函数只是一种声明,可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数
  • 友元函数只是在语法上突破了封装,调用与普通函数的调用和原理相同
#include
using namespace std;

class Date
{
	friend ostream& operator << (ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:
	Date(int year,int month,int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

private:
	int _year;
	int _month;
	int _day;
};

ostream& operator << (ostream& out,const Date& d)
{
	out << d._year << "/" << d._month << "/" << d._day << endl;
	return out;
}

istream& operator>>(istream& in, Date& d)
{
	in >> d._year;
	in >> d._month;
	in >> d._day;
	return in;
}


int main()
{
	Date d1(2022, 2, 23); //19岁的最后一天,要快乐
	Date d2(2022, 2, 24); // 等解封了,我再出去过生日yeah!
	cout << d1 << d2;
	cin >> d1 >> d2;
	return 0;
}

注意:流提取操作符,需要写入,右操作数不能用const修饰

4.2 友元类

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

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

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

  • 友元关系不能传递

    如果B是A的友元,C是B的友元,则不能说明C时A的友元。

就像是,我“宣布”你是我好朋友,那我的东西你随便拿去用好了!你不是我好朋友,那可不行,就是这样~

#include
using namespace std;

class Date; // 前置声明
class Time
{
	// 友元:声明Date为Time类的友元类,则在Date类中就直接访问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 = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

	void SetTimeOfDate(int hour, int minute, int second)
	{
		// 直接访问时间类私有的成员变量
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}

private:
	int _year;
	int _month;
	int _day;
	Time _t;
};

5. 内部类

C++不喜欢用,java喜欢用。

如果一个类定义在另一个类的内部,这个类就叫做内部类。内部类天生就是就是外部类的友元类

  • 内部类和在全局定义的类是基本一样的只是内部类受到外部类A类域限制
  • 注意友元类的是单方面的,在如下代码中,内部类B天生就是A的友元,即B可以访问A的私有和保护,A不能访问B的私有和保护。就像是我是外部类,我把你捧在手心上,什么都给你,但是我爱你又与你无关。
  • 内部类可以定义在外部类的public、protected、private都是可以的
class A {
private:
	static int k;
	int h = 0;

public:
	class B
	{
	public:
		void foo(const A& a)
		{
			cout << k << endl;	//可以访问外部类的static
			cout << a.h << endl;//可以访问外部类的private
		}
	private:
		int _b;
	};
};

int A::k = 1;

int main()
{
	A::B b; //只是受类域的限制
	b.foo(A());
	return 0;
}

sizeof(外部类) = 外部类,和内部类没有任何关系

	cout << sizeof(A) << endl;

注:static成员变量放在静态区,不在对象中

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