类和对象(3)

2024-08-17 08:21:45 浏览数 (1)

再谈构造函数

代码语言:javascript复制
class Date
{
public:
 Date(int year, int month, int day)
 {
     _year = year;
     _month = month;
     _day = day;
 }
private:
 int _year;
 int _month;
 int _day;
};

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

初始化列表

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

代码语言:javascript复制
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.成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

下面这段代码:

尽管初始化列表尝试首先初始化_a1然后初始化_a2,但实际的初始化顺序是由成员变量在类定义中的声明顺序决定的。因此,_a2会首先被初始化(使用未初始化的_a1),然后_a1才会被初始化为传入的参数a

_a2初始化时,_a1是未定义的(即处于未初始化状态),但C 标准并不要求在这种情况下编译器产生错误或警告。实际上,许多编译器会允许这种用法,但_a2会使用_a1的当前(未定义)值进行初始化,这通常是一个未定义行为(Undefined Behavior, UB),但在实践中,它可能会表现为使用某个随机值。

explicit关键字

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

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

static成员

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

1.静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区

2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明

3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问

4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员

5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制

题目:

代码语言:javascript复制
class A {  
public:  
    A() {   _scount; } // 构造函数,每当创建一个A类的新对象时,都会增加_scount的值
    A(const A& t) {   _scount; } // 拷贝构造函数,当通过拷贝另一个A类对象来创建新对象时,也会增加_scount的值
    ~A() { --_scount; } // 析构函数,每当一个A类的对象被销毁时,都会减少_scount的值
    static int GetACount() { return _scount; } // 静态成员函数,返回当前A类对象的数量
private:  
    static int _scount; // 静态私有成员,用于跟踪A类对象的数量(声明)
};  
  
int A::_scount = 0; // 在类定义外部初始化静态成员_scount,初始值为0  
  
void TestA() {  
    cout << A::GetACount() << endl; // 输出当前A类对象的数量,此时应该为0,因为还没有创建任何A类的对象
    A a1, a2; // 创建两个A类的对象a1和a2,此时A类对象的数量应该增加到2
    A a3(a1); // 通过拷贝a1来创建一个新的A类对象a3,此时A类对象的数量应该增加到3
    cout << A::GetACount() << endl; // 再次输出当前A类对象的数量,此时应该为3
}

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

不能。静态成员函数属于类本身,而非类的某个特定对象。非静态成员函数则依赖于类的特定实例,由于静态成员函数没有this指针,因此它无法访问类的非静态成员,因为这些非静态成员需要this指针来访问。

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

可以。静态成员函数属于类本身,而非类的某个特定对象。非静态成员函数则依赖于类的特定实例,所以它们可以在没有任何对象实例的情况下被调用。非静态成员函数可以通过类名直接调用静态成员函数,也可以通过this指针调用,尽管通过this指针调用并不是必要的,因为静态成员函数不属于任何特定对象实例。

友元

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

友元函数

现在尝试去重载operator,然后发现没办法将operator重载成成员函数。

因为cout的输出流对象隐含的this指针在抢占第一个参数的位置,this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator重载成 全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>>同理。

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

代码语言:javascript复制
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);

友元函数可访问类的私有和保护成员,但不是类的成员函数

友元函数不能用const修饰

友元函数可以在类定义的任何地方声明,不受类访问限定符限制

一个函数可以是多个类的友元函数

友元函数的调用与普通函数的调用原理相同

友元类

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

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

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

友元关系不能传递 如果C是B的友元, B是A的友元,则不能说明C是A的友元。

友元关系不能继承。

内部类

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

内部类是一个独立的类, 它不属于外部类,更不能通过外部类的对象去访问内部类的成员

外部类对内部类没有任何优越的访问权限。

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

特性: 1. 内部类可以定义在外部类的public、protected、private都是可以的。

2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。

3. sizeof(外部类)=外部类,和内部类没有任何关系。

匿名对象

代码语言:javascript复制
int main()
{
 A aa1;
 // 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
 //A aa1();
 // 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
 // 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
 A();
 A aa2(2);
 // 匿名对象在这样场景下就很好用,当然还有一些其他使用场景,这个我们以后遇到了再说
 Solution().Sum_Solution(10);
 return 0;
}

拷贝对象时的一些编译器优化

在传参和传返回值的过程中,编译器会做一些优化,减少一些对象的拷贝。

代码语言:javascript复制
int main()
{
 // 传值传参
 A aa1;
 f1(aa1);
 cout << endl;
 // 传值返回
 f2();
 cout << endl;
 // 隐式类型,连续构造 拷贝构造->优化为直接构造
 f1(1);
 // 一个表达式中,连续构造 拷贝构造->优化为一个构造
 f1(A(2));
 cout << endl;
 // 一个表达式中,连续拷贝构造 拷贝构造->优化一个拷贝构造
 A aa2 = f2();
 cout << endl;
 // 一个表达式中,连续拷贝构造 赋值重载->无法优化
 aa1 = f2();
 cout << endl;
 return 0;
}

再次理解类和对象

类是对某一类实体(对象)来进行描述的,描述该对象具有那些属性,那些方法描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化具体的对象。

0 人点赞