【C++】复习重点-汇总2-面向对象(三大特性、类/对象、构造函数、继承与派生、多态、抽象类、this/对象指针、友元、运算符重载、static、类/结构体)
面向对象
面向对象编程(Object-Oriented Programming,OOP)是 C++ 的核心特性之一。它提供了一种以“类”和“对象”为基础的编程范式,强调数据抽象、模块化、继承和多态等机制。
一、面向对象三大特性(核心)
特性 | 说明 | 关键字 |
---|---|---|
封装 | 把数据和操作打包到一个类中,隐藏实现细节 | private , public , protected |
继承 | 子类继承父类成员,代码复用 | class B : public A |
多态 | 相同接口表现不同行为,主要通过虚函数实现 | virtual , override , = 0 |
封装 是将数据成员和操作这些数据的函数绑定在一个类中,对外隐藏实现细节,仅暴露必要接口。它提升了模块独立性、安全性和代码可维护性。
class Account {
private:double balance;public:void deposit(double amount);double getBalance() const;
};
继承 支持创建新的类(派生类),在保留已有类(基类)特性的同时扩展新功能,从而实现代码复用和逻辑扩展。
class Person {
public:void walk();
};class Student : public Person {
public:void study();
};
多态 是指在同一接口下,表现出不同的行为形式,分为:
- 编译时多态(静态多态):通过函数重载、运算符重载实现
- 运行时多态(动态多态):通过虚函数和基类指针/引用调用派生类函数实现
二、类与对象基础
知识点 | 简要说明 | 示例关键字 |
---|---|---|
类与对象 | 类是模板,对象是实例 | class , object |
构造函数 | 创建对象时自动调用 | constructor |
析构函数 | 对象销毁前自动调用 | ~ClassName() |
成员函数 | 定义在类中的函数 | void func() |
成员变量 | 类中的变量 | int a; |
1.类
类是一种用户自定义的数据类型,定义对象的属性(成员变量)与行为(成员函数)。
类的声明:
class 类名
{public:公有数据成员;公有成员函数;private:私有数据成员;私有成员函数;protected:保护数据成员;保护成员函数;
};
2.对象
类的实例,使用类定义的结构在内存中创建实际实体。
//1.声明类同时定义对象
class 类名
{类体;
}对象名列表;
//2.先声明类,再定义对象
类名 对象名(参数列表);//参数列表为空时,()可以不写
//3. 不出现类名,直接定义对象
class
{类体;
}对象名列表;
//4.在堆上创建对象
Person p(123, "yar");//在栈上创建对象
Person *pp = new Person(234,"yar");//在堆上创建对象
对象成员的引用:
对象名.数据成员名 或者 对象名.成员函数名(参数列表)
注:不可以在定义类的同时对其数据成员进行初始化,因为类不是一个实体,不合法但是能编译运行
三、访问控制(权限修饰)
关键字 | 含义 |
---|---|
public | 公共成员,类外可访问 |
private | 私有成员,仅类内可访问 |
protected | 受保护成员,类内和子类可访问 |
和Java、C#不同的是,C++中public、private、protected只能修饰类的成员,不能修饰类,C++中的类没有共有私有之分
类内部没有访问权限的限制,都可以互相访问。
在C++中用class定义的类中,其成员的默认存取权限是private。
访问控制符限定类成员对外部访问权限:
访问修饰符 | 类内访问 | 派生类访问 | 类外访问 |
---|---|---|---|
public | ✔ | ✔ | ✔ |
protected | ✔ | ✔ | ✘ |
private | ✔ | ✘ | ✘ |
四、构造函数
类型 | 描述 |
---|---|
默认构造函数 | 无参数构造 |
有参构造函数 | 带参数构造 |
拷贝构造函数 | 用一个对象创建另一个对象 |
析构函数 | 对象销毁时自动调用,释放资源 |
构造函数初始化列表 | 用于成员变量初始化,性能更好 |
构造函数:在对象创建时自动调用,用于初始化成员变量。可重载。
析构函数:对象生命周期结束时自动调用,用于释放资源。只能定义一个,且无参数。
这里要区分 函数声明,函数定义,构造函数,我看到这里感觉构造函数与声明挺像的,所以进行了对比。
比较项 | 函数声明 | 函数定义 | 构造函数 |
---|---|---|---|
是否属于类 | 可是类内成员,也可以是类外函数 | 同上 | 是类的专属成员函数 |
作用 | 告诉编译器函数的原型 | 提供函数的具体实现 | 创建对象时自动执行初始化 |
是否含函数体 | ❌ 无函数体 | ✅ 有函数体 | ✅ 有函数体 |
名称规则 | 任意合法名称 | 与声明一致 | 必须与类名相同 |
返回值 | 有(如 int、void) | 有 | ❌ 无(连 void 也不能写) |
调用方式 | 手动调用,如 add(1, 2) | 同上 | 自动调用,如 Person p; |
是否可以重载 | ✅ 可声明多个重载版本 | ✅ 函数体可重载 | ✅ 支持多个参数列表重载 |
常见位置 | 头文件或函数体前 | 源文件或类中 | 类体内或外部定义 |
特殊性 | 普通语法结构 | 普通语法结构 | C++ 面向对象特性 |
函数声明是“告诉编译器函数长什么样”,函数定义是“告诉它怎么做”,而构造函数是“在类对象创建时自动做初始化”的专属函数,它不写返回值、名字和类名相同。
/////////////// 对比 ////////////////////
int add(int a, int b); // 只有函数签名,无实现int add(int a, int b) { // 含函数体return a + b;
}class Person {
public:Person(string name) { //构造函数cout << "Hello, " << name << endl;}
};
////////////////析构 构造 ///////////////////
class File {
public:File(); // 构造函数~File(); // 析构函数
};
///////////////// 带默认参数的构造函数 //////////////////
class Person{
public:Person(int = 0,string = "张三");void show();
private:int age;string name;
};
// 带默认参数的构造函数
Person::Person(int a, string s){cout<<a<<" "<<s<<endl;age = a;name = s;
}
//带参数初始化表的构造函数
Person::Person(int a, string s):age(a),name(s){cout << a << " " << s << endl;
}
void Person::show(){cout << "age="<<age << endl;cout << "name=" <<name << endl;
}
int main(){Person p; //0 张三Person p2(12);//12 张三Person p3(123, "yar");//123 yarreturn 0;
}
构造函数重载
class Person{
public:Person();Person(int = 0,string = "张三");Person(double,string);void show();
private:int age;double height;string name;
};
...
拷贝构造函数
类名::类名(类名&对象名)
{函数体;
}
class Person
{
public:Person(Person &p);//声明拷贝构造函数Person(int = 0,string = "张三");void show();
private:int age;string name;
};
Person::Person(Person &p)//定义拷贝构造函数
{cout << "拷贝构造函数" << endl;age = 0;name = "ABC";
}
Person::Person(int a, string s):age(a),name(s)
{cout << a << " " << s << endl;
}
int main()
{Person p(123, "yar");Person p2(p);p2.show();return 0;
}
//输出
123 yar
拷贝构造函数
age=0
name=ABC
五、继承与派生类
1.继承与派生GN
继承:在一个已有类的基础上建立一个新类,已有的类称基类或父类,新类称为派生类和子类;
派生:GN同继承,角度不同,继承是儿子继承父亲的产业,派生是父亲把产业传承给儿子。
一个基类可以派生出多个派生类,一个派生类可以继承多个基类(n*n多对多)
类型 | 说明 | 示例 |
---|---|---|
单继承 | 一个子类继承一个父类 | class B : public A {} |
多继承 | 一个类继承多个父类 | class C : public A, B {} |
虚继承 | 用于解决菱形继承问题 | class D : virtual public A |
class 派生类名 : 继承方式 基类名 {// 派生类的新成员变量和成员函数
};
常见继承方式
public(公有继承) 最常用
protected(保护继承)
private(私有继承)默认
公有继承
#include <iostream>
using namespace std;
// 基类
class Animal {
public:void eat() {cout << "Animal is eating." << endl;}
};// 派生类
class Dog : public Animal {
public:void bark() {cout << "Dog is barking." << endl;}
};int main() {Dog d;d.eat(); // 继承自 Animald.bark(); // 自己的成员函数return 0;
}
//Animal is eating.
//Dog is barking.
继承方式 | 基类 public 成员在派生类中变成 | protected 成员 | private 成员 |
---|---|---|---|
public | public | protected | 不可访问 |
protected | protected | protected | 不可访问 |
private | private | private | 不可访问 |
利用using关键字可以改变基类成员再派生类中的访问权限;
using只能修改基类中public和protected成员的访问权限。
class Base
{
public:void show();
protected:int aa;double dd;
};
void Base::show(){
}
class Person:public Base
{
public:using Base::aa;//将基类的protected成员变成publicusing Base::dd;//将基类的protected成员变成public
private:using Base::show;//将基类的public成员变成privatestring name;
};
int main()
{Person *p = new Person();p->aa = 12;p->dd = 12.3;p->show();//出错delete p;return 0;
}
2.构造函数与析构函数的调用顺序
构造顺序:先构造基类 → 再构造派生类
析构顺序:先析构派生类 → 再析构基类
#include <iostream>
using namespace std;// 基类
class Base {
public:Base() {cout << "Base 构造函数被调用" << endl;}~Base() {cout << "Base 析构函数被调用" << endl;}
};// 派生类
class Derived : public Base {
public:Derived() {cout << "Derived 构造函数被调用" << endl;}~Derived() {cout << "Derived 析构函数被调用" << endl;}
};int main() {cout << "main 开始执行" << endl;Derived obj;cout << "main 结束执行" << endl;return 0;
}
main 开始执行
Base 构造函数被调用
Derived 构造函数被调用
main 结束执行
Derived 析构函数被调用
Base 析构函数被调用
六、多态(Polymorphism)
静态多态:
- 函数重载(Overload)
- 运算符重载(Operator Overload)
动态多态:
- 使用虚函数 virtual
- 需要基类指针/引用指向派生类对象
- 支持运行时多态(Late Binding)
#include <iostream>
using namespace std;class Animal {
public:virtual void speak() { // 虚函数cout << "Animal speaks" << endl;}
};class Dog : public Animal {
public:void speak() override { // 重写父类方法cout << "Dog barks" << endl;}
};void makeSound(Animal* animal) {animal->speak(); // 运行时绑定
}int main() {Dog d;makeSound(&d); // 输出:Dog barks(多态)return 0;
}
3.多继承
多继承容易让代码逻辑复杂、思路混乱,一直备受争议,中小型项目中较少使用,后来的 Java、C#、PHP 等干脆取消了多继承。
构造与析构顺序说明:
-
构造顺序:
按照继承列表中声明的顺序依次调用 A() → B() → C()。 -
析构顺序:
先析构 C(),再反向析构 B() → A()。
#include <iostream>
using namespace std;// 基类 A
class A {
public:A() {cout << "A 构造函数被调用" << endl;}~A() {cout << "A 析构函数被调用" << endl;}void showA() {cout << "A::showA()" << endl;}
};// 基类 B
class B {
public:B() { cout << "B 构造函数被调用" << endl; }~B() {cout << "B 析构函数被调用" << endl;}void showB() { cout << "B::showB()" << endl;}
};// 派生类 C,同时继承 A 和 B
class C : public A, public B {
public:C() { cout << "C 构造函数被调用" << endl; }~C() { cout << "C 析构函数被调用" << endl; }void showC() { cout << "C::showC()" << endl;}
};
int main() {cout << "main 开始执行" << endl;C obj;obj.showA(); // 访问来自 A 的函数obj.showB(); // 访问来自 B 的函数obj.showC(); // 访问自己的函数cout << "main 结束执行" << endl;return 0;
}
main 开始执行
A 构造函数被调用
B 构造函数被调用
C 构造函数被调用
A::showA()
B::showB()
C::showC()
main 结束执行
C 析构函数被调用
B 析构函数被调用
A 析构函数被调用
4.菱形继承
A/ \B C\ /D
在这种结构下,B 和 C 都继承自 A,而 D 又同时继承自 B 和 C,这就可能会导致:
- D 中包含两份 A 的拷贝(冗余)
- 对 A 成员访问时出现二义性(ambiguous)
未使用虚继承的代码示例(会出问题)
#include <iostream>
using namespace std;class A {
public:int val;A() : val(10) {cout << "A 构造函数" << endl;}
};class B : public A {
public:B() {cout << "B 构造函数" << endl;}
};class C : public A {
public:C() {cout << "C 构造函数" << endl;}
};// D 从 B 和 C 派生
class D : public B, public C {
public:D() {cout << "D 构造函数" << endl;}
};int main() {D obj;// obj.val = 100; // ❌ 错误!val 在 B::A 和 C::A 中各有一份,二义性obj.B::val = 100; // ✅ 指定路径obj.C::val = 200;cout << "B::val = " << obj.B::val << endl;cout << "C::val = " << obj.C::val << endl;return 0;
}
A 构造函数
B 构造函数
A 构造函数
C 构造函数
D 构造函数
B::val = 100
C::val = 200
A 的构造函数被调用了两次 → 产生了两个 val
5.虚继承
使用虚继承解决菱形继承问题
class B : virtual public A { ... };
class C : virtual public A { ... };
A 构造函数
B 构造函数
C 构造函数
D 构造函数
val = 999
类型 | 是否重复构造 A | 是否有二义性 | 成员数量 |
---|---|---|---|
普通继承 | ✅ 是 | ❌ 有 | 两份 A 成员 |
虚继承 | ❌ 否 | ✅ 无 | 一份 A 成员 |
七、抽象类与接口
知识点 | 描述 |
---|---|
抽象类 | 含有纯虚函数的类 |
纯虚函数 | virtual void func() = 0; |
接口(模拟) | 纯虚函数 + 无成员变量的抽象类 |
抽象类是包含至少一个纯虚函数(= 0)的类,不能被实例化,仅用于被继承,定义接口规范。
class Animal {
public:virtual void sound() = 0; // 纯虚函数
};
八、this指针、对象指针 和 const对象
this:指针指向当前对象本身,用于区分成员与形参重名
const成员函数:承诺不修改对象状态,可被 const 对象调用
对象指针(Object Pointer):是指指向类对象的指针变量。你可以把它理解为“类的指针版变量”,它可以用来访问类的成员、动态创建对象、实现多态等。
class A {int x;
public:void set(int x) { this->x = x; }int get() const { return x; }
};class Person {
public:void sayHello() {cout << "Hello!" << endl;}
};int main() {Person p;Person* ptr = &p; // 对象指针,指向 p//ptr 是一个指针变量,类型是 Person*,指向对象 p。通过 -> 操作符访问类的成员。ptr->sayHello(); // 通过指针访问成员函数return 0;
}
动态创建对象 + 对象指针
class Person {
public:string name;void say() {cout << "I'm " << name << endl;}
};
int main() {Person* p = new Person(); // 在堆上创建对象p->name = "Tom";p->say(); // 输出:I'm Tomdelete p; // 手动释放内存return 0;
}
配合多态使用:
class Animal {
public:virtual void speak() {cout << "Animal speaks" << endl;}
};
class Cat : public Animal {
public:void speak() override {cout << "Meow!" << endl;}
};
int main() {Animal* ptr = new Cat(); // 父类指针指向子类对象ptr->speak(); // 输出:Meow!delete ptr;return 0;
}
对比项 | this 指针 | 对象指针 (Person* ) |
---|---|---|
定义方式 | 编译器隐式生成 | 程序员显式声明 |
存在位置 | 类的非静态成员函数内部 | 程序中任何地方都可定义 |
指向对象 | 当前成员函数所属的对象 | 你手动赋值的对象地址 |
是否可修改指向 | ❌ 不可更改 | ✅ 可随意更换 |
是否能访问私有成员 | ✅ 可以 | ✅ 可以(需是类内成员或友元) |
使用场景 | 成员访问歧义、链式调用、返回自身 | 访问对象成员、函数传参、多态 |
写法形式 | this->成员 | ptr->成员 |
this 是类内部默认的对象指针,指向“自己”;对象指针是开发者创建的指针,指向某个对象,二者形式相似,语义和使用时机完全不同。
九、友元(friend)
friend 函数/类可以访问私有成员,常用于操作符重载等外部函数访问类内部数据。
友元函数
- 友元函数不是类的成员函数,所以没有this指针,必须通过参数传递对象。
- 友元函数中不能直接引用对象成员的名字,只能通过形参传递进来的对象或对象指针来引用该对象的成员。
#include <iostream>
using namespace std;class Box {
private:int length;
public:Box(int l) : length(l) {}// 友元函数声明friend void printLength(const Box& b);
};// 友元函数定义(可以访问私有成员)
void printLength(const Box& b) {cout << "Length: " << b.length << endl;
}int main() {Box b(10);printLength(b); // 输出:Length: 10return 0;
}
//printLength 不是类的成员函数,但可以访问 Box 的私有成员。
友元类
一个类 A 声明另一个类 B 为它的友元类,意味着 类 B 可以访问 A 的私有成员和保护成员。
class B; // 先声明类 B,避免未定义class A {
private:int secret = 42;// B 是 A 的友元类friend class B;
};class B {
public:void showASecret(A& a) {std::cout << "A 的秘密是:" << a.secret << std::endl;}
};
//A 的秘密是:42 //类 B 通过声明为 A 的友元类,获得访问 A 的私有变量 secret 的权限。
注意事项:
特点 | 说明 |
---|---|
单向性 | A 声明 B 是友元类,B 能访问 A,但 A 不能访问 B,除非 B 也声明 A 是友元类 |
编译器视为特殊关系 | 不是继承、不是组合关系,是编译器信任授予 |
可访问私有和保护成员 | 仅访问权限,不继承特性,不改变成员属性 |
通常用于 | 操作类之间需要深入访问的情况,如调试器类、控制器类、实现细节类等 |
友元类VS友元函数
比较项 | 友元类 | 友元函数 |
---|---|---|
授权对象 | 整个类 | 单个函数 |
可访问内容 | 所有成员(包括私有/保护) | 同上 |
适合场景 | 紧耦合类之间 | 单独的辅助函数 |
友元类是类与类之间互信的一种机制,用于实现更紧密的访问控制,但应谨慎使用,避免破坏封装性和高耦合。
十、运算符重载(Operator Overloading)
C++ 允许用户重载运算符,使自定义类型支持类似内置类型的操作语义,从而提升可读性和表达力。
#include <iostream>
using namespace std;class Complex {
public:double real, imag;Complex(double r, double i) : real(r), imag(i) {}// 重载 + 运算符Complex operator+(const Complex& other) {return Complex(real + other.real, imag + other.imag);}void display() {cout << real << " + " << imag << "i" << endl;}
};int main() {Complex a(1.2, 3.4);Complex b(5.6, 7.8);Complex c = a + b; // 使用重载的 +c.display(); // 输出:6.8 + 11.2ireturn 0;
}
通过重载 +,使自定义类型像内置类型一样直观相加。
十一、类的静态成员 static
静态成员变量属于类本身而非某个对象,所有对象共享同一份数据。静态成员函数不能访问非静态成员,只能访问类中静态数据成员。
class Counter {static int count;
public:static int getCount();
};
------------------静态数据成员------------------
//类内声明,类外定义-
class xxx
{static 数据类型 静态数据成员名;
}
数据类型 类名::静态数据成员名=初值
//访问
类名::静态数据成员名;
对象名.静态数据成员名;
对象指针名->静态数据成员名;----------------静态成员函数----------------------
//类内声明,类外定义
class xxx
{static 返回值类型 静态成员函数名(参数列表);
}
返回值类型 类名::静态成员函数名(参数列表)
{函数体;
}
//访问
类名::静态成员函数名(参数列表);
对象名.静态成员函数名(参数列表);
对象指针名->静态成员函数名(参数列表);
十二、类和结构体区别
C 中只有结构体(struct)用于聚合数据;
C++ 中 class 和 struct 功能几乎相同,主要区别是默认访问权限不同,开发中通常 class 用于复杂对象,struct 用于简单数据结构。