【C++】类和对象(下)

目录

1.构造函数之回首掏(doge)

1.1构造函数体赋值

1.2 初始化列表

1.3 explicit关键字

2. static成员

2.1 概念

 2.2 特性

3.C++11 的成员初始化

4.友元

4.1友元函数

4.2友元类

5.内部类(了解)


1.构造函数之回首掏(doge)

1.1构造函数体赋值

上一节得内容中学到了在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。

class Date
{
public:
    Date(int year = 0, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
private:
    int _year;
    int _month;
    int _day;
};

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称作为类对象成员的初始化,构造函数体中的语句只能将其称作为赋初值,而不能称作初始化

因为初始化只能初始化一次,而构造函数体内可以多次赋值。
 

1.2 初始化列表

对于一些特殊类型的变量如果使用函数体赋值的方式赋初值是不能实现的。

例如:引用,const,自定义(没有默认构造函数)这些成员变量。

对于解决这些变量的初始化,C++中提供了初始化列表的方式初始化。

初始化列表则可以理解为:一个变量在定义时的初始化。

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。

举个栗子: 

class A
{
public:
	A(int x)
	{
		_x = x;
	}
private:
	int _x;
};


class Date
{
public:
	//Date(int year = 0, int month = 1, int day = 1)
	//{
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}

	// 对于const变量和引用变量,他们在定义是时必须初始化,他们不能放在函数体内初始化
	// 所以初始化列表初始化可以理解成一个变量在定义时的初始化过程

	Date(int year = 0, int month = 1, int day = 1)
		// 初始化列表初始化
		: _year(year)
		, _month(month)

		, _a(10)// const 变量
		, _b(year)// 引用变量
		, _xx(1) // A类中没有默认构造函数(不需要参数就可以调用的函数),只能显示调用
		
	{
		// 函数体内初始化
		_day = day;
	}
private:

	// 这里只是变量的声明
	int _year;
	int _month;
	int _day;

	const int _a; // 必须在定义的时候初始化
	int& _b;
	A _xx;
};

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

 注意:

           1.每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

           2.类中包含以下成员,必须放在初始化列表位置进行初始化:

                   (1).引用成员变量

                   (2).const成员变量

                   (3).自定义类型成员(该类没有默认构造函数)

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

               员变量,一定会先使用初始化列表初始化,内置类型没有区别。

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

                表中的先后次序无关
 

对于第3点,举个例子:

class Time
{
public:
	Time(int hour= 0)
		:_hour(hour)
	{
		cout << "Time()" << endl;
		cout << _hour << endl;
	}
private:
	int _hour;
};

class Date
{
public:
	Date(int day, int hour)// 不使用初始化列表,这里就会去调用他的默认构造函数
	{}                     // 就会与我们期望的结果不符,我们希望初始化_t = 20
private:                   // 而这里会初始化_t = 0
	int _day;              // 所以对自定义类型的初始化就不能使用他的默认构造函数
	Time _t;
};

int main()
{
	Date d(10,20);
	return 0;
}

对于自定义类型有一下两种初始化方法: 

class Date
{
public:
    // 初始化列表初始化 构造函数
	Date(int day, int hour)
		:_t(hour)
		,_day(day)
	{}

    // 函数体内初始化(较麻烦) 构造函数+operator+=
	//Date(int day, int hour)
	//{
	//	Time t(hour);
	//	_t = t;
	//}


private:
	int _day;
	Time _t;
};

对于第4点,举个例子:下面这段代码会输出什么?

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

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

分析:由于在类中先声明_a2, 后声明_a1, 所以在定义时会先定义a2,后定义a1;而_a2是拷贝_a1的值,_a1这时还未初始化,所以这里_a2会是随机值;_a1会被初始化为1。

1.3 explicit关键字

构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用。

用explicit修饰构造函数,将会禁止单参构造函数的隐式转换。

class A
{
public:
	A(int a)
		:_a(a)
	{}

    // explicit A(int a)
	//	:_a(a)
	//{}
private:
	int _a;
};


int main()
{
	A aa1(10);
	// 单参数的构造函数,支持隐式类型转换
	A aa2 = 20; // 早期的编译器: A tmp(20)+A aa2(tmp);
	return 0;   // 现在做了优化后:A aa2(20)
}


2. static成员

2.1 概念

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

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

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

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

#include<iostream>

using namespace std;

// 计算一个类A定义了多少个对象
class A
{
public:
	A()
	{
		++_n;
	}
	A(const A& a)
	{
		++_n;
	}
	void Print()
	{
		cout << this->Get_n() << endl;
	}

	// 静态成员函数:没有this指针,不能访问非静态成员
	static int Get_n()
	{
		return _n;
	}

private:
	// 不在构造函数初始化
	static int _n;// n存在静态区,属于整个类,也属于类的所有对象
};

// 静态成员初始化,不受访问限定符的限制(特例)
int A::_n = 0;// 这里初始化时不用加static

// 类似这里,const不能修改,但是定义的时候可以,否则没办法初始化
// const int n = 10;
// n = 20;

int main()
{
	A a;
	A a1;
	cout << sizeof(A) << endl;// 类中的成员变量是静态的,所以类的大小为1
	cout << sizeof(a) << endl;
	cout << a1.Get_n() << endl;
	cout << A().Get_n() << endl;// 如果用匿名函数的方式调用,会定义一个对象比较麻烦,所以可以增加静态成员函数
	cout << A::Get_n() << endl;// 访问方式 类名::静态函数名
	
	return 0;
}

 2.2 特性

  1. 静态成员为所有类对象所共享,不属于某个具体的实例
  2. 静态成员变量必须在类外定义,定义时不添加static关键字
  3.  类静态成员即可用类名::静态成员或者对象.静态成员来访问
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值
  6. 静态成员函数不能可以调用非静态成员函数
  7. 非静态成员函数可以调用类的静态成员函数

3.C++11 的成员初始化

C++11支持非静态成员变量声明时进行初始化赋值,但是要注意不是初始化,这里是给声明的成员变量缺省值。

class Time
{
public:
	Time(int t = 0)
	{
		_t = t;
	}

	int _t = 20;
};
class Date
{
public:
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
		cout << _a << endl;
		cout << T._t << endl;
	}
private:
    // 非静态成员变量,可以在成员声明时给缺省值。
	int _year = 2021;
	int _month = 12;
	int _day = 20;
	static int _a;
	Time T = 20;
};

int Date::_a = 10;

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

4.友元

4.1友元函数

上次的内容中学习了运算符重载,那这里能否实现对自定义类型直接输入(>>)和输出(<<)运算符的重载?

注意:C++中的 << 和 >> 是已经重载过的运算符,他们被重载在istream 和ostream两个类中。

 CppReference

class Date
{
public:
    Date(int year, int month, int day)
    : _year(year)
    , _month(month)
    , _day(day)
    {}
    
    // 这里有返回值是为了能够连续输出
    ostream& operator<<(ostream& out)// ostream& operator<<(Data*this, ostream& out)
    {
        out<<_year<<"-"<<_month<<"-"<<_day;
        return out;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d(2021, 12, 20);
    //cout<<d;  // 不能调用(this指针指向的是cout)
    d<<cout;    // 不符合使用习惯
    return 0;
}

按照以前会将operator<<重载成一个成员函数。

但是会有问题, 对于有两个操作数的操作符,第一个操作数是他的第一个参数,第二个操作数是他的第二个参数,所以这里显然不会像以前那样第一个参数是this指针,指向的是第一个参数。

而这里他的this指针指向的是cout,所以在使用时需要将两个操作时交换位置。但这样不符合我们的使用习惯。

所以这里会写成一个全局的函数。

但是也会产生问题:全局的函数中不能访问到类中的私有成员。

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

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

istream& operator>>(istream& in, Date& d)
{
    in >> d._year >> d._month >> d._day;
    return in;
}
int main()
{
    Date d(2021, 12, 20);
    cout << d << endl;
    return 0;
};

注意:

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

4.2友元类

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

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

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

  • 友元关系不能传递

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

class Time
{
	friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
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 = 2021, int month = 12, int day = 20)
		: _year(year)
		, _month(month)
		, _day(day)
	{
		// 需要访问Time类中的私有成员变量
		_t._hour = 1;
		_t._minute = 1;
		_t._second = 1;
	}

	void SetTimeOfDate(int hour, int minute, int second)
	{
		// 直接访问Time类中的私有成员变量
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}
private:
	int _year; 
	int _month;
	int _day;
	Time _t;
};

5.内部类(了解)

概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。

注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。

注意:内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元(即外部类不能访问内部类的成员)。

特性:

  1. 内部类可以定义在外部类的public、protected、private都是可以的。
  2. 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
  3. sizeof(外部类)=外部类,和内部类没有任何关系。
class A
{
private:
	static int aa;
	int n;
public:
	class B
	{
	public:
		void Print(const A& a)
		{
			cout << a.aa << endl;// 内部类可以通过对象访问外部类的成员
			cout << a.n << endl;
		}
	};
};

int A::aa = 10;

int main()
{
	A::B b;       // 创建内部类的对象b
	b.Print(A()); // 调用对象b中的成员函数,通过匿名对象访问外部类的成员
	return 0;
}

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
 

)">
< <上一篇
下一篇>>