文章目录
类的成员属性
1.私有成员(private):
- 只能被同一个类的成员函数所访问,外部函数或其它类不能访问。也就是说类的对象不能直接访问,只能通过成员函数进行访问
- 私有成员不能被继承
- private的作用就是保护数据
2.公共成员(public):
- 可以被任何函数访问,包括类的成员函数、外部函数和其他类的成员函数,但是访问要是要通过类的对象来实现的
- 提供对外的接口
3.保护成员(protected):
- 只能被同一个类的成员函数所访问,外部函数或其它类不能访问,对象也只能通过成员函数进行访问
- 存在意义就是有些数据不希望被外部所访问,但是希望它可以被继承到子类中
类的继承方式
1.私有继承:父类公共和保护属性到子类中全部变成私有属性
2.公共继承:父类公共和保护属性到子类中任保留原来的属性
3.保护继承:父类公共和保护属性到子类中全部变为保护属性
私有成员不能被继承
类内声明、类外初始化
通常情况下为了保持类的简洁,对成员函数进行类内声明、类外初始化
class StudentMsg()
{
private:
int id_;
public:
int getId();//类内声明
};
int StudentMsg::getId(){}//类外初始化
类自带的函数
只有未显示定义时,编译器才会为我们添加这些函数
1.默认构造函数
2.析构函数
3.拷贝构造函数
4.移动构造函数
4.拷贝赋值运算符重载函数
5.移动赋值运算符重载函数
class Example {
public:
// 默认构造函数
Example() {
}
// 析构函数
~Example() {
}
// 拷贝构造函数
Example(const Example& other) {
}
// 移动构造函数,加了const会导致对象无法被修改,即资源无法被转移
Example(Example&& other) noexcept {
}
// 拷贝赋值运算符
Example& operator=(const Example& other) {
std::cout << "Copy assignment operator called" << std::endl;
return *this;
}
// 移动赋值运算符
Example& operator=(Example&& other) noexcept {
std::cout << "Move assignment operator called" << std::endl;
return *this;
}
};
int main() {
// 创建对象
Example obj1;
// 拷贝构造
Example obj2(obj1);
// 拷贝赋值
Example obj3;
obj3 = obj2;
// 移动构造
Example obj4(std::move(obj3));
// 移动赋值
Example obj5;
obj5 = std::move(obj4);
return 0;
}
深拷贝和浅拷贝
1.浅拷贝:浅拷贝是指将一个对象的内容复制到另一个对象,但是如果有指针指向堆区内存,会出现两个指针指向同一块内存,释放时会造成堆区内容重复释放。
2.深拷贝:会在堆区重新开辟一块空间接收源对象的堆区内容,并创建新的指针指向指向这块内存,避免浅拷贝问题。
如果类中不定义深拷贝构造函数,默认的拷贝构造函数将执行浅拷贝,所以解决浅拷贝的方式就是自己实现拷贝构造函数,对堆区内存进行接收
拷贝构造和拷贝赋值详解
- 使用场景
当一个对象被创建并初始化的时候,编译器为它调用拷贝构造函数,当一个存在对象被里另一个对象赋值时,调用的是拷贝赋值。
拷贝构造
StudentMessage student2 = student1; // 调用拷贝构造函数
StudentMessage foo();//函数以值传递方式返回对象
void foo(StudentMessage obj);//对象以值传递的方式传给函数
拷贝赋值
StudentMessage student3(2, 22, "Jane Doe");
student3 = student1; // 调用拷贝赋值运算符
- 使用实例
主要是介绍一下编写规范
class StudentMEssage{
private:
int id_;
int *age_;
string name_;
public:
StudentMEssage(int id,int age,string name)
:id_(id),age_(new int(age)),name_(name) {}
//实现深拷贝
StudentMEssage(const StudentMEssage& other)
:id_(other.id_),age_(new int(*other.age_)),name_(other.name_) {}
//实现拷贝赋值操作符
StudentMEssage& operator=(const StudentMEssage& other){
//自检代码,如果是自己赋值给自己,直接返回this指针,否则会出现释放对象自身的资源,再试图复制同一个对象,这会导致错误。
if(this == &other){
return *this;
}
//释放已有资源
delete age_;
//深拷贝其他对象资源
id_ = other.id_;
age_ = new int(*other.age_);
name_ = other.name_;
return *this;
}
};
运算符重载
基本语法:
返回类型 operator运算符(参数列表) {
// 实现运算符的操作
}
示例:
// 重载 + 运算符
MyClass operator+(const MyClass& other) {
MyClass result;
result.value = this->value + other.value;
return result;
}
重载、重写和覆盖(隐藏)
1.重载:指的是同一作用域下的同名函数,仅参数列表不同,和返回类型无关
2.重写:子类对父类虚函数的重新实现
3.覆盖:父类和子类中有同名函数时,优先调用子类中的函数,父类函数被隐藏
友元
如果希望一个类或一个函数可以访问自己的私有成员变量,那么就将其在内部声明为friend,例如
friend class B;
friend void printWidth(Box box); // 声明友元函数
菱形继承问题
一个类从两个基类进行继承,而这两个基类又同时继承于一个类,这样会造成子孙类中有两份祖宗类的数据成员,出现二义性和行为不一致的问题
解决这个问题的方法就是虚继承,确保类中只有一份虚基类的成员
class animal{};//该类被称为虚基类
class sheep:virtual public animal{};
class tuo:virtual public animal{};
class sheeptuo:public sheep, public tuo{};
但是虚继承会引入虚基类表(virtual base table)来跟踪虚基类在派生类中的偏移量,确保只有一个实例被共享。因此,虚继承会略微增加内存开销和访问成员的开销,只有考虑多继承、菱形继承的情况下再使用虚继承
纯虚函数和抽象类
纯虚函数是在虚函数后面加上=0,如virtual void func() = 0
,拥有纯虚函数的类被称作抽象类,继承抽象类的子类必须重写这个纯虚函数。纯虚函数的意义在于定义一个接口标准,便于框架或模型的扩展
多态
1.静态多态(编译时)
- 通过模板和函数重载实现
- 编译阶段确定函数地址
2.动态多态
- 存在继承关系,派生类重写父类的虚函数
- 实现方式是父类指针或引用指向子类对象,并调用对应子类的函数
- 原理:子类对象的地址被赋给了父类对象指针,虽然使用的是父类对象的指针,但是其调用的虚函数表指针(vptr)已经是子类的了,因此调用的虚函数也是子类的
- 意义:在一些带有决策性业务代码逻辑中,运行时多态可以让程序根据用户的行为,或是执行过程的某些结果,来执行对应的函数
class Animal{
public:
virtual void makeSound(){
cout << "Some generic animal sound" << endl;
}
virtual ~Animal(){}
};
class Dog : public Animal{
public:
void makeSound() override{
cout<<"dog"<<endl;
}
};
class Cat : public Animal{
public:
void makeSound() override{
cout<<"cat"<<endl;
}
};
int main()
{
vector<Animal*> animals;
animals.push_back(new Cat());
animals.push_back(new Dog());
for(const auto& animal:animals){
animal->makeSound();
}
for(auto& animal:animals){
delete(animal);
}
system("pause");
return 0;
}
override
overrid
关键字用于显式地表明一个成员函数重写了基类中的虚函数。这有助于编译器进行检查,确保派生类中的函数确实覆盖了基类的虚函数,并且函数的签名正确。
void makeSound() override{
}
虚析构
1.目的:避免内存泄漏,在发生多态的时候,子类析构函数如果不是虚析构,无法完成与基类指针的动态绑定,这就意味着子类的析构函数不会被调用,子类对象的空间无法正确被释放,虚析构要定义在基类中
2.语法:virtual ~类名(){}
static在类中的作用
1.静态成员变量
- 类内声明类外初始化
- 编译阶段分配内存
- 所有对象共享一个变量,有一个对象对其修改,其它对象所拥有的值也随之改变
2.静态成员函数
- 没有this指针
- 静态成员函数能访问的成员变量只有静态成员变量,因为它们都属于类
- 可以直接通过类名来访问,通常我们不希望通过对象来调用的时候就定义成static,详细的可以看我这篇文章static的使用
this指针
this 指针是一个特殊的指针,它指向调用该成员函数的对象,是一个被自动传递给每个非静态成员函数的隐式参数。对于每个非静态成员函数,可以用this指针访问当前对象的地址
使用:
-
访问成员变量和方法:在成员函数内部,当局部变量的名称与成员变量重名时,可以使用 this->成员变量名来区分和访问类的成员。
-
实现链式调用:通过在成员函数中返回 *this,可以实现方法的链式调用。这种技术常用于各种流式接口和构建者(Builder)模式。
-
返回自身的引用或指针:
成员函数可以通过返回 *this 来返回当前对象的引用,或返回 this 来返回当前对象的指针。这使得成员函数可以传递当前对象的实例。
#include <iostream>
class Box {
public:
int width, height, depth;
// 构造函数
Box(int width, int height, int depth) {
this->width = width;
this->height = height;
this->depth = depth;
}
// 设置宽度并返回对象引用以支持链式调用
Box& setWidth(int width) {
this->width = width;
return *this;
}
// 设置高度并返回对象引用以支持链式调用
Box& setHeight(int height) {
this->height = height;
return *this;
}
// 计算体积
int volume() const {
return this->width * this->height * this->depth;
}
// 显示尺寸
void display() const {
std::cout << "Width: " << this->width
<< ", Height: " << this->height
<< ", Depth: " << this->depth << std::endl;
}
};
int main() {
Box box(10, 20, 30);
box.setWidth(50).setHeight(60); // 链式调用
box.display();
std::cout << "Volume: " << box.volume() << std::endl;
return 0;
}