【c++篇】:解析c++类--优化编程的关键所在(三)

感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢
个人主页:余辉zmh–CSDN博客
文章所属专栏:c++篇–CSDN博客

在这里插入图片描述

一.构造函数的初始化列表

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(2024,10,24);
    return 0;
}

在上面这段代码中,创建对象d1调用构造函数完成初始化,虽然传参数使对象d1的成员变量有了一个初始值,但是这时候并不能称为对对象成员变量的初始化,而是对成员变量进行赋初值,在之后依然可以多次赋值。**而初始化只能初始化一次。**因此就需要使用初始化列表来完成初始化。

1.2初始化列表

初始化列表是c++中构造函数的一部分,用于初始化类的成员变量。在构造函数中,初始化列表可以用于初始化常量成员变量引用成员变量,或者其他自定义类型的成员变量

初始化列表的语法如下:

classname::classname(参数列表)
    :初始化列表
{
    
    
    构造函数体
}

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

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(2024,10,24);
    return 0;
}

下面是几个关于初始化列表需要注意的点:

  • 每个变量在初始化列表中只能出现一次(也就是只能初始化一次)

  • 当类中包含常量成员变量引用成员变量以及自定义类型成员变量(该类中没有默认构造函数)时必须放到初始化列表位置进行初始化

    class A{
          
          
    public:
        A(int a)
            :_a=a
        {
          
          }
    private:
        int _a;
    };
    class B{
          
          
    public:
        B(int a,int ret)
            :_obj(a)
            ,_ret(ret)
            ,_n(10)
        {
          
          }
    private:
        //自定义类型成员变量且对象类中没有默认构造函数
        A _obj;
        //引用成员变量
        int& _ret;
        //常量成员变量
        const int _n;
    };
    
  • 对于自定义类型来说,一定会使用初始化列表初始化,所以尽量使用初始化列表完成初始化

    以下面这段代码为例:

    class Time {
          
          
    public:
    	Time(int hour=0)
    		:_hour(hour)
    	{
          
          
    		cout << "Time(int hour=0)" << endl;
    	}
    private:
    	int _hour;
    };
    
    class Date {
          
          
    public:
    	Date(int year=1, int month=1, int day=1)
    		:_year(year)
    		,_month(month)
    		,_day(day)
    	{
          
          }
    private:
        //自定义类型成员变量调用对应类的构造函数初始化列表
    	Time _t;
    	int _year;
    	int _month;
    	int _day;
    };
    int main() {
          
          
    	Date d1;
    	return 0;
    }
    
    

在这里插入图片描述

  • 成员变量在初始化列表中的初始化顺序是按照类中声明的顺序,与其在初始化列表中的前后次序无关

    我们通过一段代码来看看这个例子:

    class A {
          
          
    public:
    	A(int a)
    		:_a1(a)
    		,_a2(_a1)
    	{
          
          }
    	void Print() {
          
          
    		cout << "_a1:" << _a1 << endl;
    		cout << "_a2:" << _a2 << endl;
    	}
    private:
    	int _a2;
    	int _a1;
    };
    
    int main() {
          
          
    	A aa1(1);
    	aa1.Print();
    	return 0;
    }
    

    在这里插入图片描述

    在上面这段代码中,可以看到输出结果为a1为1,a2为随机值,起初可能我们会以为,输出结果都为1,因为我们会觉得按照初始化列表顺序是a1先初始化为1,然后a2再初始化为1。

    但事实上是按照声明中的顺序,a2先初始化为a1,但此时,a1还是随机值,所以a2就成了随机值,然后a1再初始化为1

    因此从上面的例子中我们就可以知道成员变量在初始化列表中的初始化顺序是按照类中声明的顺序,与其在初始化列表中的前后次序无关

1.3explicit关键字

explicit关键字在c++中用于修饰类的构造函数,其主要目的是防止构造函数在默认情况下被隐式的用于类型转换。当构造函数只接受单个参数时(或者全部参数都有默认值),并且没有被声明为explicit时,他既可以作为构造函数是用来创建对象,也可以作为转换函数使用,参数类型隐式的转换为类类型。

接受单个参数的构造函数的具体体现:

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

	Date& operator=(const Date& d) {
    
    
		if (this != &d) {
    
    
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}

	void Print() {
    
    
		cout << _year << "-" << _month << "-" << _day << endl;
	}

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

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

这里是隐式类型转换整形转换为自定义类型,整形2023构造一个d1的临时对象,临时对象再拷贝构造对象d1。如果在构造函数函数名前面加explicit关键字,就会禁止单参构造函数类型转换的作用

explicit Date(int year=1, int month=1, int day=1)
		:_year(year)
		,_month(month)
		,_day(day)
	{
    
    }

在这里插入图片描述

二.静态static成员

在c++中,静态成员变量和静态成员函数时类的成员,但与类的任何对象实例多无关,他们只属于类的本身,而不是类的任何对象

2.1.静态成员变量

  • 定义:静态成员变量属于类的每个对象共享的变量,存储在静态区。
  • 初始化:静态成员变量在类中声明,在类外部定义也就是进行初始化,注意不能在类的构造函数中初始化。
  • 访问:可以通过类名和域作用限定符::来访问静态成员变量,也可以通过类的对象来访问。

2.2.静态成员函数

  • 定义:静态成员函数可以在没有类对象的情况下调用,可以访问静态成员变量和静态成员函数,但是不能访问非静态成员变量和非静态成员函数(因为不属于任何类的对象)。
  • 特点:没有this指针,因为this指针指向调用它的对象,而静态成员函数不依赖任何对象;不能用const修饰,因为const修饰的成员函数不能修改任何成员变量,但是静态成员函数没有任何对象与之关联。
  • 访问:可以通过类名和域作用限定符::来调用,也可以通过类的对象来调用。

这里通过一道例题来演示静态成员的使用。“实现一个类,计算程序创建了多少个类对象”:

class A {
    
    
public:
    //构造函数
	A() {
    
    
		_scout++;
	}
    //拷贝构造函数
	A(const A& a) {
    
    
		_scout++;
	}
    //析构函数
	~A() {
    
    
		_scout--;
	}
    //静态成员函数用来返回私有静态成员变量的值
	static int Getscout() {
    
    
		return _scout;
	}
private:
    //声明一个静态成员变量用来计数
	static int _scout;
};
//静态成员变量在类中声明,在全局定义,定义时要在变量名前加类名和域作用限定符::
int A::_scout = 0;

int main() {
    
    
    //通过类名和域作用限定符来调用静态成员函数
	cout << A::Getscout() << endl;
	A a1, a2;
	cout << A::Getscout() << endl;
	A a3(a1);
	cout << A::Getscout() << endl;
	return 0;
}

在这里插入图片描述

三.友元

在c++中友元是一种特殊的类成员关系,它允许一个或多个函数或类访问另一个类的私有和保护成员。友元关系不是一种继承关系,而是一种访问控制上的特权授予。

3.1.友元函数

这里通过讲解重载operator<<来讲述友元函数的使用。(在我上一篇文章中有关于运算符重载的讲解,不清楚的可以看我上一篇文章噢)。

当我们尝试去重载operator<<为成员函数时,我们就会发现,当我们使用<<时,需要打印对象d1<<左边,cout<<右边。而我们平常使用<<时,则是cout在左边,打印对象在右边,使用的时候会感觉非常别扭。之所以会这样是因为,在重载operator<<为成员函数时,this指针默认占了第一个参数的位置,因此,使用<<的对象就成为了左操作数,也就是需要在<<的左边。但如果将operator<<重载成全局函数时,又会导致类外无法访问成员,这时候就需要友元函数来解决。

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

	ostream& operator<<(ostream& _cout) {
    
    
		_cout << _year << "-" << _month << "-" << _day << endl;
		return _cout;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main() {
    
    
	Date d1(2024, 10, 24);
    //<<运算符重载
	d1 << cout;
	return 0;
}

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

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

	friend ostream& operator<<(ostream& _cout, const Date& d);

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

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

int main() {
    
    
	Date d1(2024, 10, 24);
	cout << d1;
	return 0;
}

注意点:

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

3.2.友元类

友元类是在一个类中声明为friend的另一个类。声明后,友元类的所有成员函数都可以访问该类的私有和保护成员。

以下面的代码为例:

Time类中声明Date类为其友元类,那么Date类中的成员函数可以直接访问Time类的私有成员变量,但Time中不能访问Date类的私有成员变量。

class Time {
    
    
public:
	friend class Date;

	Time(int hour=1,int minute,int second)
		:_hour(hour)
		,_minute(minute)
		,_second(second)
	{
    
    }
private:
	int _hour;
	int _minute;
	int _second;
};

class Date {
    
    
public:
	Date(int year = 0, int month = 0, int day = 0) 
		:_year(year)
		,_month(month)
		,_day(day)
	{
    
    }
	void Getdateoftime(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;
};

注意点:

  • 友元关系是单向的,不具有交换性
  • 友元关系不能传递,比如B是A的友元,C是B的友元,不能说明C是A的友元
  • 友元关系不能继承(继承将会在之后讲解,现在只需了解一下就行)

四.内部类

如果一个类定义在一个类的内部,这个类就叫做内部类,内部是一个独立的类,他不属于外部类,更不能通过外部类的对象去访问内部类的成员。

内部类天生是外部类的友元类

  • 内部类可以定义在外部类的任何位置
  • 内部类可以直接访问外部类的静态成员,不需要外部类的对象或类名
  • sizeof(外部类)=外部类,和内部类没有关系
class A {
    
    
private:
	static int k;
	int _h;
public:
	A(int h=0)
		:_h(h)
	{
    
    }
	class B {
    
    
	public:
		void foo(const A& a) {
    
    
			cout << k << endl;
			cout << a._h << endl;
		}
	};
};
int A::k = 1;

int main() {
    
    
	A::B b;
	A a1(0);
	b.foo(a1);
	return 0;
}

在这里插入图片描述
以上就是关于c++类最后部分的讲解,如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/2301_82347435/article/details/143220329