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

目录

一、初始化列表

二、explicit 关键字

三、const成员

3.1 const数据成员

3.2 const成员函数

四、static成员

4.1 static成员变量

4.2 static成员函数

五、友元

5.1 友元函数

5.2 友元类

六、内部类 


一、初始化列表

构造函数可以使用初始化列表来对成员变量进行初始化,而不仅仅是在函数体内进行赋值操作。

初始化格式如下:

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

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

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

初始化列表的特性: 

  1.  每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
           引用成员变量、
           const成员变量、
           自定义类型成员(且该类没有默认构造函数时)
  3. 不论是否使用初始化列表,自定义类型成员变量都会先使用初始化列表进行初始化。
    如果没有使用初始化列表,对未初始化的内置类型,使用随机值初始化,
    对未初始化的自定类型,会自动调用默认构造
  4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后 次序无关。(先走初始化列表,再走函数体)
class Time
{
public:
	Time(int hour = 0)
		:_hour(hour)
	{
		cout << "Time()" << endl;
	}
private:
	int _hour;
};

class Date
{
public:
// 特性1:每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
		, _tmpi(_year)
		, _tmpc('c')
		, _t()//特性4:按照顺序先初始化_t再初始化_tmpc.
//特性3:如果此处舍去_t()的初始化,仍然会调用_t的构造函数
	{}
private:
	int _year;
	int _month;
	int _day;

//特性2:类中包含以下成员,必须放在初始化列表位置进行初始化,否则报错
	int& _tmpi;
	Time _t;
	const char _tmpc;

};

 注:C++11支持在类中给成员变量赋值,但是类不是实例化出来的对象,没有分配空间,所以给成员变量赋的值其实是在初始化列表使用的缺省值, 初始化列表是在类实例化对象时使用,属于某一个对象。


二、explicit 关键字

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值 的构造函数,还具有类型转换的作用。

在C++中,explicit是一种关键字,用于修饰类的构造函数或者转换函数。

它的作用是防止编译器进行隐式类型转换。 当一个构造函数只有一个参数时,它可以被用于隐式类型转换。有时候,我们希望禁止这种隐式转换,以避免潜在的错误或者不直观的代码。这就是 explicit 关键字的用处。 以下是 explicit 的使用例子:

class MyClass {
public:
	explicit MyClass(int val) // 构造函数使用 explicit 关键字
		: _val(val) 
	{}

private:
	int _val;
};

int main() {
	MyClass obj1(10);  // 直接调用构造函数,显式创建对象

	// MyClass obj2 = 20;  // 错误!禁止隐式类型转换
	MyClass obj2 = MyClass(20);  // 使用显式类型转换进行对象的创建

    //构造函数没有被explicit修饰,下面这条代码可执行
    //const MyClass& obj3 = 30;//临时对象具有常性,若是引用,需要const修饰
    
	return 0;
}

在上面的例子中,如果MyClass的构造函数没有用explicit修饰MyClass obj2 = 20; 是可以正常执行的,因为编译器进行隐式转换,构造了临时对象MyClass(20),然后拷贝构造给obj2。
相当于MyClass obj2 = MyClass(20);

但是MyClass的构造函数已经用explicit修饰了,所以隐式类型转换被禁止,必须使用参数相应的类型。


三、const成员

3.1 const数据成员

const 定义的常量在超出其作用域之后其空间会被释放。

在 C++ 中,const 成员变量不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数。

原因:

        const 数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其 const 数据成员的值可以不同。所以不能在类的声明中初始化 const 数据成员,因为类的对象没被创建时,编译器不知道 const 数据成员的值是什么。const 数据成员的初始化只能在类的构造函数的初始化列表中进行。

class Test {
public:
	Test() 
		:a(0) 
	{}
private:
	const int a;//只能在构造函数初始化列表中初始化
};

3.2 const成员函数

const修饰的“成员函数”称之为const成员函数

const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改,但可以访问成员变量。

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << "Print()" << endl;

	}
	void Print() const
	{
		cout << "Print()const" << endl;
	}
private:
	int _year;
	int _month;
	int _day; 
};
void Test()
{
	Date d1(1949, 10, 1);
	d1.Print();
	const Date d2(2023, 10, 1);
	d2.Print();
}

对返回值加const

部分使用场景:顺序表中的[ ]重载、Print、[ ]++、....

是否对成员函数加const,取决于内部是否修改,只读函数可以加const,但是要注意const函数的匹配问题,不能出现权限的放大。

权限可以平移或缩小,但是不可以放大

  • const对象不可以调用非const成员函数
  • 非const对象可以调用const成员函数
  • const成员函数内不可以调用其它的非const成员函数
  • 非const成员函数内可以调用其它的const成员函数

四、static成员

在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。 使用静态数据成员可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。

4.1 static成员变量

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为类的静态成员变量

static成员变量的特性:

  1. 静态成员变量一定要在类外进行初始化。
    原因:在类的内部只是声明,而静态成员变量属于类。静态成员变量不能给缺省值, 缺省值是在初始化列表使用的, 初始化列表又是在类实例化对象时使用的,属于某一个对象。通常在类的实现文件中初始化
  2. 初始化时不加该成员的访问权限控制符private、public等
  3. static 关键字只能用于类定义体内部的声明中,
  4. static成员定义时不能标示为 static,需要标注数据类型和作用域操作符:: 指明成员属于哪个类域。前面不加static,以免与一般静态变量或对象相混淆。
  5. 初始化时使用作用域运算符来表明它所属的类,因此,静态数据成员是类的成员而不是对象的成员。
  6. 如果static成员变量被const修饰,就需要定义时在前面加上const。
  7.  类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
class Test2
{
public:
	Test2()
		:a(0)
	{}
private:
	int a;
	static int b;//在类的实现文件中定义并初始化
	const static int c;
    //static const int c;//两种写法相同
};

int Test2::b = 10;//static成员变量不能在构造函数初始化列表中初始化,因为它不属于某个对象。
const int Test2::c = 20;//注意:给静态成员常量赋值时,不需要加static修饰符,但要加cosnt。

4.2 static成员函数

用 static修饰的成员函数,称之为静态成员函数。和static成员变量一样,它们都属于类的静态成员,都不是对象成员。因此,对static成员函数的引用不需要用对象名。

static成员函数特性:

  • 静态成员函数不可以调用类的非静态成员。因为静态成员函数不含this指针,非静态成员通过this指针访问。
  • 静态成员是可以独立访问的,即无须创建任何对象实例就可以访问。
  • 静态成员函数不可以调用非静态成员函数
  • 非静态成员函数可以调用类的静态成员函数
class Test2
{
public:
	Test2()
		:a(0)
	{}

	static void func();

private:
	int a;
	static int b;//在类的实现文件中定义并初始化
	const static int c;
};

int Test2::b = 10;//static成员变量不能在构造函数初始化列表中初始化,因为它不属于某个对象。
const int Test2::c = 20;//注意:给静态成员常量赋值时,不需要加static修饰符,但要加cosnt。
void Test2::func()
{
	cout << "void Test2::func()" << endl;
}

int main()
{
	Test2::func();
	return 0;
}

五、友元

在C++中,友元(friend)是一种机制,通过使用友元关键字,允许在一个类中授权其他类或非成员函数访问该类的私有成员。

友元分为:友元函数和友元类

5.1 友元函数

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

友元函数的特性

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

友元函数的使用场景:

我们要是想要使用cout <<  D1 << endl; 将日期类对象D1输出,就需要将operator<<重载成成员函数,但是成员函数的第一个指针都是隐含的this指针,就会形成下面的例子。

class Date
{
public:
	Date(int year, int month, int day)
		: _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(2023, 10, 1);
	d1.operator<<(cout);//不符合常规调用
	// 因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧
	return 0;
}

虽然结果正确,但是d1.operator<<(cout);不是我们想要的写法。所以我们就需要将第一个参数给_cout,第二个给日期类,同时函数需要能够访问到Date类中的private成员。于是我们在类外定义普通函数,并且声明它为Date类的友元函数。

class Date
{
public:
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	friend ostream& operator<<(ostream& _cout, const Date& d1);
private:
	int _year;
	int _month;
	int _day;
};

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

int main()
{
	Date d1(2023, 10, 1);
	cout << d1 << endl;
	return 0;
}

5.2 友元类

可以将一个类声明为另一个类的友元类,使友元类能够访问目标类的私有成员和保护成员。一个类可以将多个其他类声明为友元类,并且友元关系不具有传递性。

友元类的特性:

  • 友元类的所有成员函数都可以是另一个类的友元函数。都可以访问另一个类中的非公有成员。
  • 友元关系是单向的,不具有交换性。
  • 友元关系不能传递
  • 友元关系不能继承
class Square
{
	friend class Rectangle;
public:
	Square(int sideLen = 5)
		:_sideLen(sideLen)
	{}

private:
	int _sideLen;
	int _perimeter;
	int _area;
};

class Rectangle
{
public:
	Rectangle(int length = 0, int width = 0)
		:_length(length)
		,_width(width)
	{}
	int Area()
	{
		return _length * _width;
	}
	int Perimeter()
	{
		return (_length + _width) * 2;
	}

	void SetSquare(int sideLen)
	{
		//直接访问Square类私有的成员变量
		_s._sideLen = sideLen;
		Rectangle tmp(sideLen, sideLen);
		_s._area = tmp.Area();
		_s._perimeter = tmp.Perimeter();
	}

private:
	int _length;
	int _width;
	Square _s;
};

int main()
{
	Rectangle r1(3,3);
	r1.SetSquare(6);
	return 0;
}

在上面的示例中,Rectangle 被声明为Square的友元类,Rectangle的成员函数可以直接访问Square的private成员。

友元关系应该谨慎使用,因为它可能破坏封装性,降低代码的可维护性。合理使用友元机制可以在某些特定情况下提供灵活性和方便性


六、内部类 

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

内部类可以直接访问外部类的成员,包括私有成员。内部类与外部类具有一定的关联性,并且可以享有一些特殊的访问权限。但内部类是一个独立的类, 它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。

内部类的特性:

  • 内部类是外部类的友元类,但是外部类不是内部类的友元。
  • 内部类不受外部类访问限定符的限制。
  • 内部类可以直接访问外部类中的static成员不需要外部类的对象/类名。
  • sizeof(外部类)=外部类,和内部类没有任何关系
class A
{
private:
     static int k;
     int h;
public:
    class B // B天生就是A的友元
    {
    public:
        void foo(const A& a)
        {
            cout << k << endl;//OK
            cout << a.h << endl;//OK
        }
    };
};
int A::k = 1;

 注:内部类的定义通常放在外部类的声明中,但也可以单独定义,只需在内部类的名称前加上外部类的名称和作用域运算符(::)

class OuterClass::InnerClass{

//内部类的定义

}; 

内部类在某些情况下可以提供更好的封装和组织代码的方式,然而,过度使用内部类可能会增加代码的复杂性和可读性。因此,在设计类层次结构时,应考虑使用内部类的必要性和适用性。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值