栏目分类:
子分类:
返回
终身学习网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
终身学习网 > IT > 软件开发 > 后端开发 > C/C++/C#

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

C/C++/C# 更新时间:发布时间: 百科书网 趣学号

目录

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成员变量,必须在定义的时候同时初始化。如果在函数体内初始化就会报错 ——

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

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

没有默认构造函数的自定义类型成员

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

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

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

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. 建议尽量使用初始化列表,对于自定义成员变量,初始化列表可以提高效率。

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

  • 不使用初始化列表

  • 使用初始化列表

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

    代码如下 ——

    #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。运行结果 ——

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

    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)

    这儿本来是用2022构造一个临时对象Date(2022),再用这个对象拷贝构造d2。但是C++在连续的过程中,编译器会优化多个构造,合二为一,因此这里被优化为直接就是一个构造。

    相当于这两句代码 ——

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

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

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

    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 可知,cout和cin对于内置类型之所以能“自动识别类型”,是因为库里面已经把函数重载都写好了。

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

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

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

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

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

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

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

    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成员变量放在静态区,不在对象中

  • 转载请注明:文章转载自 www.051e.com
    本文地址:http://www.051e.com/it/743402.html
    我们一直用心在做
    关于我们 文章归档 网站地图 联系我们

    版权所有 ©2023-2025 051e.com

    ICP备案号:京ICP备12030808号