C++基础笔记10
结构体区别
struct
和class
仅仅只有默认的访问控制不同
struct
默认是public,class
默认是private
如果数据只是简单的聚合就用struct
,如果用于表示行为和状态的复杂对象,不能随意让外部访问,则用class
(这种访问控制的能力就是封装性,类的重要特性)
通过类定义的变量称之为对象
类的操作
访问控制:private, protected, public, friend
用于初始化的函数称为构造函数
析构函数用于对象销毁时做清理,调用时机是对象被销毁时,不管是栈中的对象被自动销毁,还是new
出来的对象被手动delete
,都会自动调用析构函数
成员函数 在对象内写的函数(只有public才能被外部调用,如果是private只能在成员函数内被调用)
成员函数的this
指针指向对象自身,默认会传递
运算符重载(操作符重载)可以让内置的运算符进行重解释,可以将函数名改成对应的操作符,需要加上operator
关键字
1 | class BigInt { |
一般运算符都是由左右两部分组成的 lhs operator rhs
重载运算符的操作就是右边的操作数auto operator-(const Type &rhs){}
explicit
关键字代表必须显式转化,类型转化运算符使用,避免因为二义性带来问题
friend
友元,在类中声明并加上友元声明,就可以在其他位置进行访问该类的私有成员,也可以单独对类的某个成员函数声明为友元friend double BigFloat::pow(BigInt&);
成员函数的其他命名方法:BigInt BigInt::add(BigInt &X) {}
继承与多态
继承:继承另一个类的成员变量和函数,方便复用
可访问父类非私有的成员,继承方式影响成员在子类的权限
1 | class Obj { |
子类可以访问public和protected成员,不能访问private成员
关键字 ==final== 如果一个类被final修饰,则这个类不能被继承
构造函数无法被继承,需要显式声明,先父再子
析构函数无法被继承,先子后父,建议写成虚函数
纯虚函数不需要实现,但是在子类中必须重写
虚函数的实现原理是虚函数表
虚函数 —> 动态多态
1 | class Zombie { |
例子:高精度大数
1 |
|
重载运算符
在C++中,重载运算符是否加&
(引用符号)主要取决于运算符的语义和使用场景。以下是详细说明:
加&
的情况
返回左值引用的情况
- 当重载的运算符需要返回一个左值(可以被赋值的对象)时,通常会加
&
。例如,重载赋值运算符=
时,通常返回一个左值引用,以便支持链式赋值。这样可以支持如下链式赋值:1
2
3
4MyClass& operator=(const MyClass& rhs) {
// 实现赋值操作
return *this;
}1
2MyClass a, b, c;
a = b = c; - 重载下标运算符
[]
时,也通常返回左值引用,以便可以对返回的对象进行赋值操作。这样可以支持如下操作:1
2
3
4int& operator[](size_t index) {
// 返回数组元素的引用
return data[index];
}1
obj[0] = 10; // 通过下标运算符返回的引用进行赋值
- 当重载的运算符需要返回一个左值(可以被赋值的对象)时,通常会加
- 避免不必要的拷贝
- 当重载运算符的返回值是一个较大的对象时,为了避免不必要的拷贝,可以返回引用。例如,重载输入运算符
>>
时,通常返回istream&
,以避免拷贝输入流对象。1
2
3
4istream& operator>>(istream& in, MyClass& obj) {
// 从输入流读取数据到obj
return in;
}
- 当重载运算符的返回值是一个较大的对象时,为了避免不必要的拷贝,可以返回引用。例如,重载输入运算符
不加&
的情况
- 返回右值的情况
- 当重载的运算符需要返回一个临时对象(右值)时,不加
&
。例如,重载加法运算符+
时,通常返回一个临时对象。这里返回的是一个临时的1
2
3
4
5MyClass operator+(const MyClass& rhs) const {
MyClass temp(*this);
temp += rhs;
return temp;
}MyClass
对象,而不是引用。
- 当重载的运算符需要返回一个临时对象(右值)时,不加
- 返回布尔值的情况
- 当重载的运算符返回布尔值时,也不加
&
。例如,重载比较运算符==
、!=
等。1
2
3
4bool operator==(const MyClass& rhs) const {
// 比较逻辑
return true; // 或 false
}
- 当重载的运算符返回布尔值时,也不加
- 返回指针的情况
- 当重载的运算符返回指针时,也不加
&
。例如,重载成员访问运算符->
时,返回一个指针。1
2
3MyClass* operator->() {
return this;
}
- 当重载的运算符返回指针时,也不加
特殊情况
- 重载下标运算符
[]
的常量版本- 如果重载的下标运算符是常量成员函数,需要返回一个常量引用,以保证返回的对象不能被修改。
1
2
3const int& operator[](size_t index) const {
return data[index];
} - 这样可以防止通过下标运算符修改对象的成员变量。
- 如果重载的下标运算符是常量成员函数,需要返回一个常量引用,以保证返回的对象不能被修改。
总结
- 加
&
:当需要返回左值引用(如赋值运算符、下标运算符等)或者避免不必要的拷贝(如输入运算符)时。 - 不加
&
:当返回临时对象(如加法运算符)、布尔值、指针等情况时。
根据具体的运算符语义和使用场景选择是否加&
,可以更好地实现代码的效率和语义正确性。
动态联编
在面向对象编程中,动态联编(Dynamic Binding) 是一种机制,允许在运行时根据对象的实际类型来调用相应的函数。基类指针动态联编调用派生类的函数,通常涉及到 多态(Polymorphism) 和 虚函数(Virtual Function)。以下为你详细解释并举例说明:
基本概念
- 基类与派生类 :基类是定义了一些通用属性和行为的类,派生类是从基类继承而来的类,可以继承基类的成员,并且可以添加新的成员或重写(Override)基类的成员函数。
- 虚函数 :在基类中声明为
virtual
的函数。当通过基类指针或引用调用虚函数时,会根据对象的实际类型来调用相应的函数,而不是根据指针或引用的类型。这是实现动态联编的关键。 - 动态联编 :在运行时根据对象的实际类型来确定调用哪个函数。与之相对的是静态联编(Static Binding),静态联编是在编译时就确定调用哪个函数。
举例说明
假设有一个基类 Animal
和两个派生类 Dog
和 Cat
,基类中有一个虚函数 makeSound()
,派生类重写了这个函数。
1 |
|
在这个例子中:
Animal
是基类,Dog
和Cat
是派生类。makeSound()
是基类中的虚函数,Dog
和Cat
分别重写了这个函数。- 在
main()
函数中,创建了一个基类指针animalPtr
,然后分别将它指向Dog
和Cat
对象。 - 当通过
animalPtr
调用makeSound()
函数时,会根据animalPtr
指向的对象的实际类型(Dog
或Cat
)来调用相应的函数,而不是调用基类Animal
中的makeSound()
函数。这就是动态联编的体现。
如果 makeSound()
函数不是虚函数,那么通过基类指针调用函数时,就会调用基类中的函数,而不会根据对象的实际类型调用派生类中的函数,这就是静态联编的行为。