C++中的类与对象3

1 const 修饰类的成员函数

const 修饰的类的成员函数称之为 const 成员函数,const 修饰成员函数实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改

在这里插入图片描述

1 const 对象可以调用非 const 成员函数吗?
const Date* this -----> Date* this 这其实权限的放大,从可读变为可读可写,这当然是不行的。
2 非 const 对象可以调用 const 成员函数吗?
原理和1 相似,Date* this -----> const Date* this,当然是可以的
3 const 成员函数内可以调用其它的非 const 成员函数吗?
不可以
4 非 const 成员函数内可以调用其它的 const 成员函数吗?
可以

2 取地址及 const 取地址操作符重载

这两个默认成员函数一般不用重新定义 ,编译器默认生成的一般就可以满足日常开发。

class Date
{
    
     
public :
 Date* operator&()
 {
    
    
 return this ;
 }
 
 const Date* operator&()const
 {
    
    
 return this ;
 }
private :
 int _year ; // 年
 int _month ; // 月
 int _day ; // 日
};

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取指定的内容!

3 再谈构造函数

3.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 a; a = 10; 而下面要谈到的初始化列表相当于 int a = 10;

3.2 初始化列表

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

class Date
{
    
    
public:
	Date(int year = 1996,int month = 11,int day = 01)
		:_year(year)
		,_month(month)
		,_day(day)
	{
    
    }
	~Date(){
    
    
		cout << "~Date()" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

3.2.1 初始化列表中需要注意的问题

  • 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  • 类中包含以下成员,必须放在初始化列表位置进行初始化
    1 引用成员变量
    2 const成员变量
    3 自定义类型成员(该类没有默认构造函数)
class A {
    
    
public:
 A(int a)
 :_a(a)
 {
    
    }
private:
 int _a;
};
class B {
    
    
public:
 B(int a, int ref)
 :_aobj(a)
 ,_ref(ref)
 ,_n(10)
 {
    
    }
private:
 A _aobj; // 没有默认构造函数
 int& _ref; // 引用
 const int _n; // const 
};
  • 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使
    用初始化列表初始化。
  • 成员变量在类中的声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
    例如下面的程序
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();
}

结果是1 和 随机值

3.3 explicit关键字

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

#include <iostream>
#include <windows.h>
using namespace std;

class Date
{
    
    
public:
	Date(int year)
		:_year(year)
	{
    
    
		cout << "Date(int year)" << endl;
	}
	Date(const Date& d)
	{
    
     
		cout << "Date(const Date& d)" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	Date& operator=(const Date& d)
	{
    
    
		cout << "Date& operator=(const Date& d)" << endl;
		if (&d != this)
		{
    
    
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	Date d1(2019);
	Date d2 = 2021;
	//d2 = 2030;
	system("pause");
	return 0;
}

运行结果如下:

在这里插入图片描述

我们发现调用了两次构造函数,这涉及到了隐式类型转换=====>先构造一个Date(2021)的匿名临时对象,再用这个临时对象去拷贝构造d2,最后编译器把这两个过程优化合并了,直接调用了构造函数。我们修改下代码如下:

int main()
{
    
    
	Date d1(2019);
	//Date d2 = 2021;
	d1 = 2021;
	system("pause");
	return 0;
}

在这里插入图片描述

我们发现调用了两次构造函数,和一次赋值运算符重载:匿名临时对象Date(2021)调用一次构造函数,然后d1 = Date(2021);调用了一次赋值运算符重载函数。
如果不想要单参数发生隐式类型转换的,可以在构造函数前加上explicit关键字

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

在这里插入图片描述

4 static成员

4.1 概念

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的
成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化

4.2 特性

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

5 C++11 中成员变量缺省值

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

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;
 int* p = (int*)malloc(4);
 static int n;
};
int A::n = 10;
int main()
{
    
    
 A a;
 a.Print();
 return 0;
 }

非静态成员变量,可以在成员声明时给缺省值。
但是这里不是初始化,是给的缺省值,构造函数在没有给值的时候,就会用这里的缺省值,
弥补了C++98中编译器自动生成的构造函数,对内置类型不处理的问题。

6 友元

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

6.1 友元函数

class Date
{
    
    
public:
 Date(int year, int month, int day)
 : _year(year)
 , _month(month)
 , _day(day)
 {
    
    }
 
 ostream& operator<<(ostream& _cout)
 {
    
    
 _cout<<d._year<<"-"<<d._month<<"-"<<d._day;
 return _cout;
 }
 prvate:
 int _year;
 int _month;
 int _day
};
int main()
{
    
    
   Date d(2017, 12, 24);
   d<<cout;
   return 0;
}

问题:现在我们尝试去重载operator<<,然后发现我们没办法将operator<<重载成成员函数。因为cout的
输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是
实际使用中cout需要是第一个形参对象,才能正常使用。所以我们要将operator<<重载成全局函数。但是这
样的话,又会导致类外没办法访问成员,那么这里就需要友元来解决。operator>>同理。

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

class Date
{
    
    
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, 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& _cout, const Date& d) {
    
    
 _cout<<d._year<<"-"<<d._month<<"-"<<d._day;
 
 return _cout; }
istream& operator>>(istream& _cin, Date& d) {
    
    
 _cin>>d._year;
 _cin>>d._month;
 _cin>>d._day;
 
 return _cin; }
int main()
{
    
    
 Date d;
 cin>>d;
 cout<<d<<endl;
 return 0;
 }

注意:

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

6.2 友元类

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

  • 友元关系是单向的,不具有交换性。比如下面的Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time
    类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
class Date; // 前置声明
class Time
{
    
    
 friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成
员变量
public:
 Time(int hour, int minute, int second)
 : _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 _t;
  };
  • 友元关系不能传递,如果B是A的友元,C是B的友元,则不能说明C时A的友元。

7 内部类

概念及特性

概念 :如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的
类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中
的所有成员。但是外部类不是内部类的友元。
特性:
1. 内部类可以定义在外部类的public、protected、private 都是可以的
2. 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
3. sizeof(外部类)=外部类,和内部类没有任何关系。

class A {
    
    
private:
	static int k;
	int h;
public:
	class B
	{
    
    
	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());
	system("pause");
	return 0;
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/CZHLNN/article/details/114380423
今日推荐