类和对象基础语法:
1、struct默认是public,而class默认是private。struct的设计目的是让外部的程序访问其数据成员,class设计的目的之一是不让外部程序直接访问其数据成员。
2、静态数据成员属于类,而不像普通的数据成员那样属于某个对象,多个对象共享一个静态成员变量。因此我们可以用“类名::”这样的形式访问静态数据成员,包括静态变量和静态函数。如:Student::count。静态数据成员不能在类的构造函数和.h文件中进行初始化。因为类中不给他分配内存空间(前面说的类中存储地址是堆栈,静态数据成员存储地址是静态存储空间)。静态数据成员在所有实例之间共享状态或计数时非常有用。
3、前置声明也用于解决循环依赖的问题,比如在两个类相互引用时,可以在一个类之前使用另一个类的前置声明,而无需在头文件中包含另一个类的.h文件,但是需要在.cpp文件中包含前置类所在的头文件,来告知编译器这两个类的存在,允许编译器先处理前置类的引用,而不需要提前知晓具体的实现细节。例如:
class Ana_Conv;
class Post :{
需要在构造函数中传入Ana_Conv对象。
}
.cpp文件中#include "ana_conv.h"
4、this 是 C++ 中的一个关键字,也是一个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员。this 只能用在类的内部,通过 this 可以访问类的所有成员,包括 private、protected、public 属性的。this指针泛指当前类的对象,而创建的对象指的是某个固定的对象。this
指针 (this pointer) 是一个隐式的值,做为额外的实参传递给类的每个非静态成员函数。在成员函数中使用 this 指针可以更加明确地表示操作的是成员变量而非局部变量。
5、函数重载指的是在同一作用域中,定义多个同名函数,但这些函数的参数列表(参数的数量、类型或者顺序)必须不同。编译器会依据调用函数时所提供的实参,来决定调用哪一个重载函数。
通过operator 运算符(运算符参数)实现运算符重载。例如:vector& operator+ (vector& r_vec)
class Vector {
private:
std::vector<double> elements;
public:
Vector(std::initializer_list<double> list) : elements(list) {}
// 重载 + 运算符
Vector operator+(const Vector& other) const {
std::vector<double> result;
for (size_t i = 0; i < elements.size(); ++i) {
result.push_back(elements[i] + other.elements[i]);
}
return Vector(result);
}
};
int main() {
Vector v1 = {1, 2, 3};
Vector v2 = {4, 5, 6};
Vector v3 = v1 + v2;
return 0;
}
虚函数和继承:
1、继承
现在我们假设基类是一个快要退休的富豪。代码如下:
class RichMan { public: RichMan(); ~RichMan(); int m_company; private: int m_money; int m_car; friend class Partners;//Partners是RichMan的友元类 protected: int m_house;
};
继承符号 :
公司是public的,那么他自己(基类),创业伙伴(友元)可以访问,儿子(子类),其他人(外部)都可以访问。
房子是protected的,自己(基类),创业伙伴(友元)可以访问,儿子(子类)也可以访问,外人是不可以访问。
钱和车子是private的,自己(基类),创业伙伴(友元)可以访问。儿子和外人都不给开。
子类可以访问父类非私有成员函数,同时为了保证类的封装性,尽量通过调用父类的成员函数访问父类中的私有成员变量。
3、将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类对象,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。C++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。每个对象都有一个虚函数表,里面存储着所有虚函数指针。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。
4、虚函数:
C++的虚函数主要作用是“运行时多态”,父类中提供声明并用virtual关键字标记的函数的实现,为子类提供默认的函数实现,子类可以直接使用父类的虚函数。子类也可以重写或重载父类的虚函数实现子类的特殊化,子类声明虚函数前最好也加上virtual。子类调用重写或重载的虚函数时,不会调用父类的虚函数。子类对象可以赋值给父类对象,但是父类对象无法赋值给子类对象。new子类对象时,Father fa = new Son();fa->func(),调用的是子类的虚函数。在子类的重写函数后面加上override表明该函数是重写函数,避免书写错误创建一个新函数。多态是子类重载或重写了父类的方法,运行时根据对象的实际是哪个子类确定调用哪个子类的方法。
5、纯虚函数:
C++中的纯虚函数更像是“只提供声明,没有实现”,声明时virtual关键字在返回值类型之前,纯虚函数的末尾用 “= 0” 标记,它在基类中没有具体的实现,必须在派生类中实现,是对子类的约束。只要包含一个纯虚函数的类必然是抽象类,抽象类不能声明对象。抽象类中不一定都是纯虚函数。
6、将子类指针作为实参传入声明为父类指针形参的函数中,可以在函数中通过dymanic_pointer_cast将父类的shared_pointer转为子类的对象指针,进而调用子类的成员函数。
构造和析构函数
1、构造函数和析构函数都没有返回值。如果没有声明构造函数,编译器将为类提供一个默认的不带参数的构造函数和析构函数。构造函数中如果带参数,则声明对象时必须带参数。一般通过构造函数带参数或者基类构造函数带参数,将参数传递给成员变量。
2、初始化的三种方式:在类中初始化、成员列表初始化和在构造函数中初始化。如果只是为成员变量提供一个默认值,则直接在.h文件中赋值;如果成员变量和构造函数传入的参数有关系,则通过初始化列表赋值;如果成员变量的初始值需要复杂的计算或者静态成员变量,则可以在构造函数中实现。
初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段。
class Student{
private:
char *m_name;
int m_age;
float m_score;
public:
Student(char *name, int age, float score);
void show();
};
//采用参数初始化表
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){
//TODO:
}
:m_name(name), m_age(age), m_score(score)语句的意思相当于函数体内部的m_name = name; m_age = age; m_score = score;语句,也是赋值的意思。
3、explicit修饰的构造函数必须显示调用,不可隐式转化。
4、直接调用构造函数创建的对象是在栈上,会在作用域结束的时候自动释放。
5、控制反转技术旨在解决组件之间的高耦合问题,使得组件之间的依赖关系变得松散,提高软件的可维护性、可测试性和可扩展性。
实现方法:通过外部将依赖对象注入到目标对象中,而不是目标对象自己创建依赖对象。一般通过构造函数和接口函数实现依赖对象注入。
不使用控制反转示例:
class A{};
class B{
A a = new A();//如果不使用控制反转技术,则需要在B类中实现A类的实例
};
耦合度高:如果想要更换A类的实现方式,就需要在B类中修改代码;
可测试性差:如果在对B类进行单元测试时,无法替换不同的A类对象,实现多种不同情况测试。
控制反转示例:
class A{};
class B{
A m_a;
B(A a):m_a(a){}//通过构造函数实现控制反转
GetAObj(A a);//通过接口函数实现控制反转
};
int main(){
A a = new A();
B b = new B(a);//通过B的构造函数将a传入B类中
b.GetAObj(a);//通过接口函数将a传入B类中
return 0;
}