类和对象(下)

目录

构造函数:再次深挖

构造函数函数体赋值

初始化列表

概念

 为什么有初始化列表?

explicit 关键字

static 成员

引入

概念

特性

小结

C++11 成员初始化的新方式

友元

概念

友元函数

友元类

内部类


构造函数:再次深挖

构造函数函数体赋值

”构造函数函数体赋值”指的是在构造函数的函数体内部对类的数据成员进行赋值操作

初始化列表

概念

初始化列表是 C++ 中用于在构造函数中对类成员进行初始化的一种机制。

当创建一个类的对象并调用其构造函数时,可以在构造函数的参数列表之后使用冒号开始 : 逗号分隔,接着列出要初始化的成员及其初始值,这就是初始化列表。(也可以写成一行,以下这种写法可读性更好)

 为什么有初始化列表?

因为有些成员必须在初始化列表初始化

  1. 没有默认构造函数的类成员:如果一个类成员所属的类没有默认构造函数,那么在包含该成员的类的构造函数中,就只能通过初始化列表来对其进行初始化。
  2. 引用成员:引用在声明时必须被初始化,且之后不能再重新绑定到其他对象,所以也要在初始化列表中进行初始化。
  3. const 成员:由于 const 成员的值在其声明后不能被修改,所以必须在初始化列表中进行初始化。

当我们在main函数中初始化变量的时候,这三种成员必须初始化,和在类中必须初始化一个道理


在类中,这三类成员必须使用初始化列表初始化

引用和const都必须在定义的时候初始化,而对象需要调用构造函数,但是构造函数又需要传参,所以必须在定义的时候初始化

class A
{
public:
    A(int n)  // 不是默认构造函数,如果加个缺省参数就是默认的构造函数(或者不写)
    {
        _n = n;
    }
private:
    int _n;
};

class B
{
public:
    // 可以理解成初始化列表是对象的成员变量定义的地方
    B(int a, int ref, int num)  // 增加一个参数用于初始化引用
        : _aobj(1)  //没有默认构造函数,必须传参过去
        , _ref(ref)  // 初始化引用,ref 是一个已经存在的 int 变量
        , _n(10)   // 因为是const成员,必须声明时候就初始化,之后不能修改
    {}
private:
    // 成员变量的声明
    A _aobj;  // 没有默认构造函数(不用传参就可以调的那个构造函数)
    int& _ref;  // 引用
    const int _n;  // const
};

通常情况下,除了 const 成员、引用成员和没有默认构造函数的类成员这三类必须在初始化列表中进行初始化之外,其他普通成员变量既可以在初始化列表中初始化,也可以在构造函数体内进行赋值。

但一般建议在初始化列表中进行初始化,这样效率更高且代码更清晰。

class A
{
public:
    A(int n)  
    {
        _n = n;
    }
private:
    int _n;
};

class B
{
public:
    B(int a, int ref, int num) 
        : _aobj(1) 
        , _ref(ref) 
        , _n(10)
       // ,_x(num)  在初始化列表初始化
    {
        _x = num;  // 在函数体内赋值
    }
private:
    A _aobj;  
    int& _ref;  
    const int _n; 

    //其它成员变量
    int _x;
};

尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先去使用初始化列表初始化(在有默认构造函数的情况下,如果没有默认构造函数,必须在初始化列表初始化)

class Time 
{
public:
	Time(int n = 0) 
	{
		_n = n;
	}
private:
	int _n;
};
class Date 
{
public:
	Date(int day)
	{
		Time t(1);
		_t = t;
	}
private:
	int _day;
	Time _t;
};

尽管在 Date 类的构造函数中,在函数体内对 _t 进行了赋值,但实际上,对于自定义类型成员变量 _t,编译器会先尝试使用初始化列表来进行初始化。

 

由于没有在初始化列表中对 _t 进行初始化,编译器会先调用 Time 类的默认构造函数(因为在 Time 类中提供了一个带默认值的构造函数)来初始化 _t,然后再执行构造函数体内的赋值操作。

                                                                                                      

如果没有默认的构造函数,必须使用初始化列表初始化

一般来说,为了提高效率和代码的清晰性,对于自定义类型成员变量,建议在初始化列表中进行初始化。


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

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();
	return 0;
}

 初始化的顺序和声明的顺序一样,先初始化_a2,  _a2为随机值,在初始化_a1为1,通常情况下,我们都是把声明和定义一一对应起来初始化列表


explicit 关键字

在 C++ 中,explicit 关键字主要用于修饰单参数的构造函数,其作用是禁止隐式类型转换。

C++11中,可以修饰多参数的构造函数,但是这种多参数的隐式类型转换相对较少,并且需要满足特定的条件

int main()
{
	Date d1(1); // 构造
	Date d2 = 2; //隐式类型转换,构造出 tmp(2),再用tmp拷贝构造d2(tmp) = 优化成直接构造
	Date d3 = d1; //拷贝构造

    
    //和上面也是同理
	int i = 1;
	double d = i; //会先进行隐式类型转换,生成一个临时的 double 值,然后将这个临时值赋值给 d
    //如果使用引用,也会产生临时变量,而临时变量具有常性,所以需要加上const
    //double& d  = i;
    const double& d = i; 
}

不想让这种隐式类型转换发生就加上这个关键字 explicit 


C++11中,允许有多个参数赋值

static 成员

引入

我们都知道类只有在构造和拷贝构造的时候会产生对象,但是以下的缺陷是,谁都可以对n进行修改,这样计算可以,但是失去了封装性的意义

概念

在 C++ 中,static 成员是指在类中声明为 static 的成员变量或成员函数,称为静态成员变量或静态成员函数。

static 成员变量:

1,不属于类的任何对象,而是被类的所有对象共享。

2,即使没有创建类的对象,也可以通过类名直接访问和操作。

3,静态成员变量一定要在类外进行初始化

静态成员函数:

也满足前面提到的前两点:

但是静态成员函数不需要在类外进行初始化,因为函数的定义本身就可以在类内完成。

静态函数没有this指针,属于整个类,而非静态函数属于某个对象,因为含有this指针,需要传参(传对象过去) ,隐含的this指针接收,通过this指针可以访问私有成员,而static不能访问私有成员,也不能调用类里面的其它函数,因为没有this指针

非静态的函数有this指针,所以可以调用类中的任何的函数,包括静态函数

class A {
public:
    void f1() {  // 非静态成员函数,可以通过 this 指针访问私有成员
        this->_n = 10;
    }

    static void f2() {  // 静态成员函数,没有this指针,无法访问私有成员
        // 错误:无法通过 this 指针访问 _n
        // this->_n = 20; 
    }
private:
    int _n;
};

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

class A 
{
public:
	A() 
	{
		++_count;
	}
	A(const A& t) 
	{
		++_count;
	}
    static int GetCount() 
	{
		return _count;
	}
private:
	static int _count;
};
A f1(A a) 
{
	return a;
}
//类外定义静态成员变量
int A::_count = 0;
int main()
{
	A a1;
	A a2;
	f1(a1);
	f1(a2);
	cout << A::GetCount() << endl; // 6
    // 如果类中的Getcount函数不加static就需要用具体的对象调用该函数
    //cout << a2.GetCount() << endl;
	return 0;
}

特性

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

问题:

静态成员函数可以调用非静态成员函数吗

非静态成员函数可以调用类的静态成员函数吗

调用静态成员函数的方式

小结

  1. static成员变量不存在对象中,存在静态区,属于这个类的所有对象,也是属于这个类。
  2. static成员函数,没有this指针,不使用对象就可以调用。 类名 : : func();
  3. static成员函数中,不能访问非静态的成员(成员变量+成员函数)
  4. 访问限定符对 static 成员同样生效,例如如果 static 成员被声明为 private ,则在类外不能直接访问。

C++11 成员初始化的新方式

非静态成员可以在声明时给缺省值,静态成员不行

以下不是定义,而是在声明的时候给缺省值

class Date 
{
public:
	Date() 
		:_year(10)   //不使用缺省值0
	{}
	void Print() 
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	//C++11 声明时给缺省值
	int _year = 0;  //如果初始化了就用初始化的值,没有初始化就用缺省值
	int _month = 1;
	int _day = 1;
};
int main() 
{
	Date d1;
	d1.Print(); // 10-1-1
	return 0;
}

友元

概念

友元分为:友元函数和友元类,友元提供了一种突破封装的方式,被指定为友元的函数或类可以访问该类的私有成员和保护成员,就好像它们是公共成员一样。有时提供了便利,但是友元会增加耦合度,破坏了封装,所以友元不宜多用

友元函数

  1. 友元函数是在类外面访问类的私有成员或保护成员的一种方式。
  2. 但需要注意的是,虽然友元函数提供了这种访问权限,但过度使用友元函数可能会削弱类的封装性和信息隐藏原则。
  3. 比如,如果多个函数都被设为友元,可能会导致类的内部实现细节过度暴露,使得后续对类的修改和维护变得复杂。所以,在设计类时,应尽量通过提供公有成员函数来控制对私有和保护成员的访问,只有在极少数必要的情况下才使用友元函数。
  4. 友元函数的意思就是,类外面的函数想访问类内部的私有或者公有成员,就把外面的函数变成友元函数


在类外面想访问类里面的成员,可以借助友元,但是感觉这个东西不太合理,我还不如直接把函数写到类里面(成员函数),有些地方必须用友元

1,在了解某种场景必须使用友元之前,我们先知道输入和输出的类型

2,当自定义类型想想内置类型一样使用  <<   输出流运算符的时候,必须重载

3,重载该运算符

cout 传给this,d1传给了out,这传的反了,所以写成 d1 << cout,我选择用运算符重载就是为了提高可读性
我必须写成cout << d1;这种情况下cout在前,传给隐含this就是cout

把这个重载函数写到日期类里面,但是日期类对象抢了第一个隐含的位置,又想访问该成员函数,日期类又抢了第一个位置,这种情况下如何解决

 

这种情况下我们必须使用友元函数解决

class Date 
{
public:
	void Print() 
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	//运算符的重载,友元函数的声明
	friend void operator<<(ostream& out, const Date& d);
private:
	int _year = 0;
	int _month = 1;
	int _day = 1;
};
//友元函数
void operator<<(ostream& out, const Date& d) 
{
	out << d._year << "/" << d._month << "/" << d._day << endl;
}
int main()
{
	Date d1;
	cout << d1;
	// cout.operator(&cout,d1);
	return 0;
}

代码优化:有时候我们需要连续输出

class Date 
{
public:
	Date(int year = 0, int month = 1, int day = 1) 
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() 
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	//运算符的重载,友元函数的声明
	friend ostream& operator<<(ostream& out, const Date& d);
private:
	int _year = 0;
	int _month = 1;
	int _day = 1;
};
//友元函数
ostream& operator<<(ostream& out, const Date& d) 
{
	out << d._year << "/" << d._month << "/" << d._day << endl;
	return out;  
}
int main()
{
	Date d1(2024, 7, 24);
	Date d2(2024, 7, 25);
	//有时候我们会连续输出
	cout << d1 << d2 << endl;
	return 0;
}

4,输入运算符 >> 的重载 ,和输出运算符重载同理

class Date 
{
public:
	Date(int year = 0, int month = 1, int day = 1) 
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() 
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	//运算符的重载,友元函数的声明
	friend istream& operator>>(istream& in, Date& d);
private:
	int _year = 0;
	int _month = 1;
	int _day = 1;
};

//输入需要改变 d 对象不加const
// Date& d 如果不加 引用,d1 传给 d,d1拷贝构造了d,里面的改变不会影响外面d1
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}
int main()
{
	Date d1(2024, 7, 24);
	cin >> d1;
	d1.Print(); 
	return 0;
}

5,为什么能自动识别类型,因为存在函数重载,自动匹配类型 ?

这是因为 C++ 的输入输出流(iostream)库对不同的数据类型进行了重载。

重载的 operator<< 函数针对不同的类型有不同的实现,能够根据传入的参数类型自动选择正确的处理方式来进行输出。输入流也是这样的。

 

友元类

如果一个类 A 被另一个类 B 声明为友元类,那么类 B 的所有成员函数都可以访问类 A 的私有成员和保护成员。

由于 ClassB 被声明为 ClassA 的友元类,所以 ClassB 的成员函数 accessClassA 能够直接访问 ClassA 的私有成员 privateMember

 

友元类的使用需要谨慎,因为它在一定程度上破坏了类的封装性。

class ClassA 
{
private:
    int privateMember;

public:
    friend class ClassB;  // 将 ClassB 声明为友元类
};

class ClassB 
{
public:
    void accessClassA(ClassA obj) 
    {
        cout << obj.privateMember << endl;  // 可以访问 ClassA 的私有成员
    }
};

内部类

内部类(也称为嵌套类)是在另一个类的内部定义的类。

 

内部类的主要特点包括:

 
  1. 内部类的作用域被限制在其外部类的范围内。
  2. 内部类可以访问外部类的私有成员和保护成员。
//内部类
class A 
{
private: 
	static int k;
	int h;
public:
	class B //B天生就是A的友元
	{
	public:
		void foo(const A& a) 
		{
			cout << k << endl;  // 静态成员变量,B是A的友元可以访问私有
			cout << a.h << endl;
		}
	};
};
int A::k = 1;
int main()
{
	A::B b;  //嵌套的类必须指定在哪个个类下
	b.foo(A());
	//b.foo(A()); 的意思是创建一个临时的 A 类对象,
	// 并将其作为参数传递给 b 对象的 foo 函数。
    //具体来说,A() 会创建一个匿名的、临时的 A 类对象,
	// 然后通过 b.foo(...) 调用 B 类中 foo 函数,
	// 并将这个临时创建的 A 对象作为参数传递给 foo 函数进行处理。
}
  • 11
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值