C++面向对象

续接上回“2026 前端新出路:掌握 C++ 核心语法,无缝衔接 QT 桌面开发
,开始进入c++的面向对象的学习!

学过 Java / JavaScript 面向对象的同学,重点关注 C++ 的差异点即可。


一、C++ 中的类和对象是什么?

1.1 从现实世界看"类"和"对象"

在面向对象编程里,对象是两个最核心的概念。

  • 类(Class):是对某一类事物的抽象描述——描述这类事物有什么属性(数据)和能做什么(行为)。类是图纸,不是实物。
  • 对象(Object):是类的具体实例。图纸照进现实造出来的一个个真正存在的东西。

举个例子:

对象
现实世界 "汽车"这个抽象概念 你小区楼下那辆白色丰田
代码里 class Student Student zhangsan;
类比 制作饼干的模具 用模具压制出来的一块块饼干

一句话:类是模板/蓝图,对象是用模板造出来的实体。

1.2 C++ 中类和对象的关系

// 类 —— 抽象的,一个定义
class Student {
public:
    string name;
    int age;
    void study() {
        cout << name << " 在学习" << endl;
    }
};

// 对象 —— 具体的,可以有无数个
int main() {
    Student s1;    // s1 是一个对象,有实实在在的内存空间
    Student s2;    // s2 是另一个对象,和 s1 互不影响

    s1.name = "张三";
    s1.age = 20;

    s2.name = "李四";
    s2.age = 21;

    s1.study();    // 输出:张三 在学习
    s2.study();    // 输出:李四 在学习

    cout << sizeof(s1) << endl;  // s1 有确定的大小(占用实际内存)
    // cout << sizeof(Student) << endl;  // ❌ 不行,类名不能直接取大小
    return 0;
}

关键认知

  • 类本身不占用运行时内存(代码段里存定义),对象才占用栈/堆内存
  • 同一个类可以创建无数个对象,各自的数据独立存储

二、类的创建(定义)

2.1 类的基本语法

class 类名 {
private:              // 私有成员——只能在类内部访问
    成员数据;
    成员函数;

public:               // 公有成员——外部可以通过对象访问
    成员数据;
    成员函数;

protected:            // 保护成员——类内部和派生类可以访问
    成员数据;
    成员函数;

};  //  注意:这个分号绝对不能少!

2.2 三种访问修饰符

修饰符 类内部 派生类(子类) 外部(通过对象)
private ✅ 可访问 ❌ 不可访问 ❌ 不可访问
protected ✅ 可访问 ✅ 可访问 ❌ 不可访问
public ✅ 可访问 ✅ 可访问 ✅ 可访问

封装的核心思想:把数据设为 private,通过 public 的成员函数来间接操作。这就是 getter/setter 的由来。

class BankAccount {
private:
    double balance;     // 余额——不允许外部直接改

public:
    double getBalance() {       // 只读访问
        return balance;
    }

    void deposit(double amount) {  // 受控的修改通道
        if (amount > 0) {
            balance += amount;
        }
    }
};

int main() {
    BankAccount acc;
    // acc.balance = -10000;  //  编译错误!private 成员不能直接访问
    acc.deposit(50000);        //  通过 public 函数间接操作
    cout << acc.getBalance();  // 正确
    return 0;
}

2.3 成员函数的两种实现方式

方式一:类内定义(隐式内联)

class Student {
public:
    void sayHello() {           // 直接在类里写函数体
        cout << "Hello!" << endl;
    }
};

方式二:类内声明 + 类外定义

class Student {
public:
    void sayHello();            // 类内只声明(接口)
};

void Student::sayHello() {      // 类外定义(实现)
    cout << "Hello!" << endl;   // 必须用 类名:: 前缀
}
方式 优点 缺点
类内定义 简洁,自动内联 头文件暴露实现细节
类外定义 接口与实现分离(.h + .cpp) 需要写 类名::

::作用域解析运算符,表示这个函数属于哪个类。

2.4 一个完整的类定义示例

#include <iostream>
#include <cstring>
using namespace std;

class Stu {
private:
    int sno;              // 学号
    char sname[10];       // 姓名
    double dscore;        // 分数

public:
    // 类内定义
    void InputData() {
        cout << "\n请输入学号: ";
        cin >> sno;
        cout << "请输入姓名: ";
        cin >> sname;
        cout << "请输入分数: ";
        cin >> dscore;
    }

    // 类内声明
    void OutPutData();
    double daverage;     // 公有数据成员(一般不建议,仅演示)
};

// 类外实现
inline void Stu::OutPutData() {
    cout << "\n学生数据信息如下:" << endl;
    cout << "学号: " << sno << endl;
    cout << "姓名: " << sname << endl;
    cout << "分数: " << dscore << endl;
}

三、对象的创建与使用

3.1 创建对象的多种方式

#include <iostream>
using namespace std;

class Stu {
public:
    int sno;
    Stu() { cout << "构造函数调用" << endl; }
    ~Stu() { cout << "析构函数调用" << endl; }
};

int main() {
    // 方式 1:栈上创建(最常用)
    Stu stu1;               // 自动调用构造函数
    stu1.sno = 1001;
    // 离开 main 时自动析构——不需要手动释放

    // 方式 2:堆上创建(动态分配)
    Stu* pStu = new Stu;    // new + 构造函数
    pStu->sno = 1002;
    delete pStu;            // 必须手动 delete,否则内存泄漏!delete 时自动析构

    // 方式 3:数组
    Stu arr[3];             // 3 个 Stu 对象,调用 3 次构造函数
                            // 离开作用域自动析构 3 次

    // 方式 4:动态数组
    Stu* pArr = new Stu[3]; // 3 个对象
    delete[] pArr;          // 注意:必须是 delete[],不是 delete

    return 0;
}
创建方式 内存位置 生命周期 访问成员 是否手动释放
Stu s; 离开 {} s.成员 自动
Stu* p = new Stu; 直到 delete p->成员 必须
Stu arr[5]; 离开 {} arr[i].成员 自动
Stu* p = new Stu[5]; 直到 delete[] p[i].成员 必须

3.2 访问对象的成员

class Demo {
public:
    int value;
    void show() { cout << value << endl; }
};

int main() {
    // 通过对象名访问:用 .
    Demo obj;
    obj.value = 42;
    obj.show();

    // 通过指针访问:用 ->
    Demo* ptr = &obj;
    ptr->value = 100;    // 等价于 (*ptr).value = 100;
    ptr->show();         // 等价于 (*ptr).show();

    // 通过引用访问:用 .
    Demo& ref = obj;
    ref.value = 200;
    ref.show();

    return 0;
}
访问方式 语法 场景
对象名 obj.成员 栈上的对象
指针 ptr->成员 堆上的对象 / 指针参数
引用 ref.成员 函数引用参数

3.3 结合原文的完整使用示例

#include <iostream>
using namespace std;

class Stu {
private:
    int sno;
    char sname[10];
    double dscore;
public:
    void InputData() {
        cout << "\n请输入学号: ";
        cin >> sno;
        cout << "请输入姓名: ";
        cin >> sname;
        cout << "请输入分数: ";
        cin >> dscore;
    }
    void OutPutData();
    double daverage;
};

// 类外实现
void Stu::OutPutData() {
    cout << "\n学生数据信息如下:" << endl;
    cout << "学号: " << sno << endl;
    cout << "姓名: " << sname << endl;
    cout << "分数: " << dscore << endl;
}

Stu stu2;  // 全局对象——见第五节

void testFunc() {
    stu2.daverage = 100;
    cout << "全局对象 stu2 的平均分: " << stu2.daverage << endl;
}

int main() {
    Stu stu1;           // 创建对象
    stu1.InputData();   // 调用成员函数(输入数据)
    stu1.OutPutData();  // 调用成员函数(输出数据)
    testFunc();         // 访问全局对象
    return 0;
}

四、类作用域

4.1 什么是类作用域?

类名 {} 内部的所有成员(成员变量、成员函数、typedef、枚举等)都属于类作用域。这意味着:

  • 类内成员不能从外部直接通过名字调用,必须通过对象/指针/引用 + 点运算符或箭头运算符访问。
  • 即使是 public 成员,也必须在类作用域外使用 对象.成员 的形式。

4.2 为什么要有类作用域?

类作用域是 C++ 封装的基石之一。它把属于一个类的名字"锁在"类内部,防止与其他类或全局名字冲突。两个不同类可以有同名的成员函数,互不干扰:

class Dog {
public:
    void speak() { cout << "汪汪" << endl; }
};

class Cat {
public:
    void speak() { cout << "喵喵" << endl; }
};

这两个 speak() 分别属于两个不同的类作用域,永远不会冲突。

4.3 不能在 main 中直接通过函数名调用

这是类作用域最直接的后果——不能直接写函数名

class Stu {
public:
    void OutPutData() { cout << "学生数据" << endl; }
};

int main() {
    //  错误!OutPutData 不在全局作用域,它在 Stu 类作用域里
    // OutPutData();

    //  正确:必须通过对象调用
    Stu s;
    s.OutPutData();          // 点运算符

    Stu* p = &s;
    p->OutPutData();         // 箭头运算符
    return 0;
}

核心规则:类成员函数不能像普通函数那样裸调,必须绑定到具体对象上调用。

4.4 类作用域中的名字查找顺序

当你在类的成员函数内部使用一个名字时,编译器按如下顺序查找:

1. 成员函数内部的局部变量
2. 该类的成员变量/成员函数
3. 全局作用域(::)

示例:

int score = 60;  // 全局变量

class Stu {
private:
    int score = 90;  // 成员变量
public:
    void showScore() {
        int score = 100;                        // 局部变量(优先级最高)
        cout << "局部: " << score << endl;       // 100
        cout << "成员: " << this->score << endl; // 90
        cout << "全局: " << ::score << endl;     // 60
    }
};

4.5 类成员的访问控制 + 类作用域的叠加效果

即使 public,也必须带上对象——“外部可访问"不等于"可以直接用名字”。


五、类类型作用域(文件作用域 & 块作用域)

5.1 文件作用域(全局作用域)

文件作用域指的是定义在所有函数、所有类之外的变量/函数。它们从定义处开始,到文件末尾结束,整个文件都可以访问。

#include <iostream>
using namespace std;

// ↓ 文件作用域(全局作用域)
int globalCount = 0;           // 全局变量,整个文件可见

class Counter {
public:
    void increment() {
        globalCount++;          // 类成员函数中访问全局变量 —— 允许
    }
};

void printGlobal() {
    cout << "全局计数: " << globalCount << endl;  // 普通函数中也允许
}

int main() {
    Counter c;
    c.increment();
    c.increment();
    printGlobal();              // 输出: 全局计数: 2
    cout << globalCount << endl;// main 中也能直接访问
    return 0;
}

关键点

  • 在原文中,Stu stu2; 就是一个文件作用域的全局对象
  • 文件作用域里的东西可以被该文件里的任何函数、任何类访问
  • 如果另一个 .cpp 文件想用,需要用 extern 声明

5.2 块作用域

块作用域{} 内部定义的变量,只在那个大括号内有效。

int main() {
    int a = 10;     // a 的作用域从这里开始...
    {
        int b = 20; // b 的作用域从这个内层 {} 开始
        cout << a;  //  a 在外层块,内层可以访问
        cout << b;  //  b 在内层块,可以访问
    }               // b 的作用域到此结束

    cout << a;      //  a 还在作用域内
    // cout << b;   //  编译错误!b 已经超出作用域
    return 0;
}
// 类成员函数内部的块作用域
class Demo {
public:
    void test() {
        int x = 5;              // 整个 test() 函数内有效
        if (x > 0) {
            int y = 10;         // y 只在 if 块内有效
            cout << x << y;     // 
        }
        cout << x;              // 错误
        // cout << y;           //  y 已销毁
    }
};

5.3 文件作用域 vs 块作用域——名字隐藏

int value = 999;  // 文件作用域

class Test {
public:
    int value = 100;  // 类作用域(成员变量)

    void show(int value) {  // 参数 value —— 块作用域(优先级最高)
        cout << "参数 value: " << value << endl;            // 使用参数
        cout << "成员 value: " << this->value << endl;      // 使用成员
        cout << "全局 value: " << ::value << endl;          // 使用全局
    }
};

int main() {
    Test t;
    t.show(1);      // 参数=1, 成员=100, 全局=999
    return 0;
}

名字隐藏规则:内层作用域的名字会隐藏外层同名的名字。 想访问被隐藏的,就需要 this->(成员)或 ::(全局)。


六、对象的作用域(全局 / 局部 / 局部静态)

6.1 全局对象

定义在所有函数、所有类之外的对象,属于文件作用域。它在 main() 执行之前就构造好了,main() 结束之后才析构。

#include <iostream>
using namespace std;

class Stu {
public:
    int sno;
    Stu()  { cout << "全局对象构造" << endl; }
    ~Stu() { cout << "全局对象析构" << endl; }
};

Stu stu2;  // ← 全局对象(对应原文中的 Stu stu2)

void testFunc() {
    stu2.sno = 2023001;   // 任何函数都可以访问
    cout << "学号: " << stu2.sno << endl;
}

int main() {
    cout << "=== main 开始 ===" << endl;
    testFunc();
    cout << "=== main 结束 ===" << endl;
    return 0;
}

/* 输出顺序:
   全局对象构造       ← 在 main 之前!
   === main 开始 ===
   学号: 2023001
   === main 结束 ===
   全局对象析构       ← 在 main 之后!
*/
特点 说明
生命周期 整个程序运行期间
构造时机 main() 之前
析构时机 main() 之后
作用域 定义处至文件末尾(整个文件);其他文件需 extern
默认初始化 数值类型初始化为 0

6.2 局部对象

在函数/块内部定义的对象,属于块作用域。当程序执行到它的定义语句时构造,离开所在的 {} 时自动析构。

#include <iostream>
using namespace std;

class Stu {
public:
    int sno;
    Stu()  { cout << "  构造" << endl; }
    ~Stu() { cout << "  析构" << endl; }
};

void func() {
    Stu localStu;   // ← 局部对象,进来时构造
    localStu.sno = 1001;
    cout << "  func 内部,学号: " << localStu.sno << endl;
}                   // ← 局部对象离开作用域,自动析构

int main() {
    Stu stu1;       // ← 局部对象(原文中的 Stu stu1)
    stu1.sno = 9527;
    cout << "main 中, 学号: " << stu1.sno << endl;

    func();
    // localStu 已经不存在了,不能访问

    cout << "main 即将结束" << endl;
    return 0;
}                   // stu1 在此析构

/* 输出:
    构造              ← stu1
  main 中, 学号: 9527
    构造              ← localStu
    func 内部, 学号: 1001
    析构              ← localStu(func 结束)
  main 即将结束
    析构              ← stu1(main 结束)
*/
特点 说明
生命周期 仅在所在 {} 内有效
构造时机 执行到定义语句时
析构时机 离开所在的 {} 时(析构顺序与构造相反)
作用域 所在块内
默认初始化 不初始化,成员值是未定义的(垃圾值)

6.3 局部静态对象

在函数内部用 static 关键字修饰的局部对象。它只在第一次执行到定义语句时构造一次,直到程序结束才析构。

#include <iostream>
using namespace std;

class Stu {
public:
    Stu()  { cout << "  静态对象构造(仅一次)" << endl; }
    ~Stu() { cout << "  静态对象析构(程序结束时)" << endl; }

    int sno;
    void showAddr() {
        cout << "  对象地址: " << this << endl;
    }
};

void callMe() {
    static Stu staticStu;   // ← 局部静态对象!只构造一次
    staticStu.sno++;
    cout << "  调用次数: " << staticStu.sno << endl;
    staticStu.showAddr();
}

int main() {
    cout << "第 1 次调用 callMe():" << endl;
    callMe();   // 构造 + sno=1

    cout << "第 2 次调用 callMe():" << endl;
    callMe();   // 不构造!sno=2

    cout << "第 3 次调用 callMe():" << endl;
    callMe();   // sno=3

    cout << "main 结束" << endl;
    return 0;
}

/* 输出:
  第 1 次调用 callMe():
    静态对象构造(仅一次)
    调用次数: 1
    对象地址: 0x7ff6c2345000
  第 2 次调用 callMe():
    调用次数: 2
    对象地址: 0x7ff6c2345000    ← 同一个地址!
  第 3 次调用 callMe():
    调用次数: 3
    对象地址: 0x7ff6c2345000    ← 还是同一个!
  main 结束
    静态对象析构(程序结束时)
*/
特点 说明
生命周期 整个程序运行期间(和全局对象一样长)
构造时机 第一次执行到定义语句时(不是 main 之前)
析构时机 程序结束时
作用域 仅在定义它的函数内部可见(和局部对象一样窄)
初始化次数 只初始化一次

🎯 局部静态对象的精髓:全局对象的长生命周期 + 局部对象的窄可见范围。

6.4 三种对象对比总结

类型 定义位置 生命周期 可见范围 初始化次数
全局对象 所有函数/类之外 程序始终 整个文件(其他文件需 extern 1 次
局部对象 函数/块 {} 所在块内 所在块内 每次进入块都初始化
局部静态对象 函数内加 static 程序始终 所在函数内 1 次(第一次进入时)

6.5 完整示例

把原文中三种对象都包含的完整代码:

#include <iostream>
using namespace std;

class Stu {
private:
    int sno;
    char sname[10];
    double dscore;
public:
    void InputData() {
        cout << "请输入学号: ";
        cin >> sno;
        cout << "请输入姓名: ";
        cin >> sname;
        cout << "请输入分数: ";
        cin >> dscore;
    }
    void OutPutData();
    double daverage;
};

Stu stu2;  // ← 全局对象(文件作用域)

void Stu::OutPutData() {
    cout << "\n学生数据信息如下:" << endl;
    cout << "学号: " << sno << endl;
    cout << "姓名: " << sname << endl;
    cout << "分数: " << dscore << endl;
}

void testFunc() {
    static int callCount = 0;   // ← 局部静态变量(类比局部静态对象)
    callCount++;
    stu2.daverage = 100;
    cout << "\n[testFunc 第 " << callCount << " 次调用]" << endl;
    cout << "全局对象 stu2 的平均分为:" << stu2.daverage << endl;
}

int main() {
    Stu stu1;  // ← 局部对象(块作用域,main 内)

    stu1.InputData();
    stu1.OutPutData();

    testFunc();  // 第 1 次
    testFunc();  // 第 2 次——callCount 保留上次的值

    return 0;
}  // stu1 在此析构(局部对象)
   // stu2 在程序结束后析构(全局对象)

七、类的嵌套

要点:

  • 嵌套类本质上是一个独立的类,只是作用域在外部类内部
  • 外部类和内部类的同名成员互不影响,各有独立内存空间
  • 内部类成员函数在类外定义时需写 外部类::内部类::函数名
#include <iostream>  // c++ 包含头文件输入输出流
#include <iomanip>
using namespace std;

class CC1 {
public:
	int x;
	void Func();
	class CC2 {
	public:
		int x;
		void Func();
	}objc;
};

void CC1::Func() {
	x = 3000;
	cout << "x = " << x << endl;
}

void CC1::CC2::Func() {
	x = 4000;

	cout << "x = " << x << endl;
}

int main() {
	class CC1 obj;
	obj.Func();  // x = 3000
	obj.objc.Func();  // x = 4000
	cout << "\n\n";
	cout << "2:x = " << obj.x << endl; // 2:x = 3000
	cout << "2:x = " << obj.objc.x << endl; // 2:x = 4000
	return 0;
}

要点:

  • 嵌套类本质上是一个独立的类,只是作用域在外部类内部
  • 外部类和内部类的同名成员互不影响,各有独立内存空间
  • 内部类成员函数在类外定义时需写 外部类::内部类::函数名

八、对象引用私有数据成员

8.1 通过公有函数为私有成员赋值

最常用的封装模式——setter / getter

#include <iostream>  // c++ 包含头文件输入输出流
#include <iomanip>
using namespace std;

// 通过共有函数,为私有数据成员赋值
class Ctest {
	int x, y;
public:
	void setxy(int a, int b) {
		x = a;
		y = b;
	}
	void dispxy() {
		cout << "x=" << x << ",y=" << y << endl << endl;
	}
};

int main() {
	Ctest Obj1;
	Obj1.setxy(78, 90); // 调用共有成员函数为私有对象赋值
	Obj1.dispxy();
	return 0;
}

8.2 利用指针访问私有数据成员

通过公有函数将私有成员的值"传出"到外部变量(传指针)。

#include <iostream>  // c++ 包含头文件输入输出流
#include <iomanip>
using namespace std;

// 通过共有函数,为私有数据成员赋值
class Ctest {
	int x, y;
public:
	void setxy(int a, int b) {
		x = a;
		y = b;
	}
	void printxy() {
		cout << "x=" << x << ",y=" << y << endl << endl;
	}
	void getxy(int* px, int* py) {
		*px = x;
		*py = y;
	}
};

int main() {
	Ctest obj;
	obj.setxy(100, 200);
	obj.printxy(); // x=100,y=200
	int n, m;
	obj.getxy(&m, &n);
	cout << "\n\nm=" << m << ",n=" << n << endl << endl;   // m=100,n=200

	return 0;
}

注意: 参数 int* px 是值传递(传递的是地址值本身),但通过解引用 *px 可以修改外部变量。

8.3 利用函数访问私有数据成员

最简洁的方式——通过返回值传出。

#include <iostream>  // c++ 包含头文件输入输出流
#include <iomanip>
using namespace std;

// 通过函数访问私有数据成员
class Ctest {
	int x, y;
public:
	void setxy(int a, int b) {
		x = a;
		y = b;
	}
	void printxy() {
		cout << "x=" << x << ",y=" << y << endl << endl;
	}
	int getX() {
		return x;
	}
	int getY() {
		return y;
	}
};

int main() {
	Ctest obj;
	obj.setxy(100, 200);
	obj.printxy();
	cout << "\n\nx=" << obj.getX() << ",y=" << obj.getY() << endl << endl;   // m=100,n=200 

	return 0;
}

8.4 利用引用访问私有数据成员

通过引用参数直接把外部变量"绑定"到私有成员的值。

#include <iostream>  // c++ 包含头文件输入输出流
#include <iomanip>
using namespace std;

// 通过引用访问私有数据成员
class Ctest {
	int x, y;
public:
	void setxy(int a, int b) {
		x = a;
		y = b;
	}
	void printxy() {
		cout << "x=" << x << ",y=" << y << endl << endl;
	}
	void getxy(int& px, int& py) {
		px = x;
		py = y;
	}
};

int main() {
	Ctest obj;
	obj.setxy(100, 200);
	obj.printxy();
	int m, n;
	obj.getxy(m, n);
	cout << "\n\nm=" << m<< ",n=" << n << endl << endl;   // m=100,n=200 

	return 0;
}

四种方式对比:

方式 语法 特点
公有函数赋值 obj.setxy(a, b) 最标准,封装性好
指针传出 obj.getxy(&m, &n) 可以一次传出多个值,调用方需取地址
返回值传出 obj.getX() 最简洁,一个函数只能返回一个值
引用传出 obj.getxy(m, n) 语法最自然,调用方无需取地址

九、成员函数的重载

类中的成员函数与前面介绍的普通函数一样,成员函数可以带有缺省的参数,也可以重载成员函数,重载时,函数的形参必须在类型数目上不同。

9.1 缺省参数的成员函数

#include <iostream>  // c++ 包含头文件输入输出流
#include <iomanip>
using namespace std;

class CTest {
    int x, y;
    int m, n;
public:
    void setxy(int a, int b) {
        x = a;
        y = b;
    }
    void setxy(int a, int b, int c, int d) {
        x = a;
        y = b;
        m = c;
        n = d;
    }
    void dispxymn(int x) {
        cout << x << "," << y << endl;
    }
    void dispxymn() {
        cout << x << "," << y << "," << m << "," << n << endl<< endl;
    }
};

int main() {
    CTest obj1, obj2;

    // 参数不同
    obj1.setxy(10, 20);
    obj2.setxy(10, 20, 30, 40);

    // 参数类型不同
    obj1.dispxymn(666);
    obj2.dispxymn();
    return 0;
}

重载规则:

  • 函数名相同,参数列表(类型或个数)必须不同
  • 不能仅靠返回值类型区分重载
  • 编译器根据调用时的实参自动匹配最合适的版本

9.2 定义类的指针及如何用指针来引用对象

可以定义指向类对象的指针,通过 -> 运算符访问成员。

#include <iostream>  // c++ 包含头文件输入输出流
#include <iomanip>
using namespace std;

class CTest {
public:
    double sum() {
        return x + y;
    }
    void setxy(double a, double b) {
        x = a;
        y = b;
    }
    void dispxy() {
        cout << x << "," << y << endl;
    }
private:
    int x, y;
};

int main() {
    CTest obj1, obj2; // 定义对象
    CTest* pobj; // 定义类的指针(对象指针)
    pobj = &obj1;
    pobj->setxy(3.4, 8.9); // 通过指针引用对象的成员函数

    pobj->dispxy();

    cout << "x+y=" << pobj->sum() << endl;
    return 0;
}

C++之面向对象

9.3 定义类的数组及数组中元素的引用

可以定义类的数组,每个元素都是一个独立的对象。

#include <iostream>
using namespace std;

class CTest {
    int x, y;
public:
    void setxy(int a, int b) { x = a; y = b; }
    void print() { cout << "(" << x << ", " << y << ")" << endl; }
};

int main() {
    CTest arr[3];           // 对象数组,3 个 CTest 对象

    arr[0].setxy(10, 20);
    arr[1].setxy(30, 40);
    arr[2].setxy(50, 60);

    for (int i = 0; i < 3; i++) {
        cout << "arr[" << i << "] = ";
        arr[i].print();
    }

    // 对象数组 + 指针
    CTest* p = arr;         // 指向数组首元素
    p->print();             // arr[0] → (10, 20)
    (p + 1)->print();       // arr[1] → (30, 40)

    return 0;
}

十、this指针

不同对象占据内存中不同区域,它们所保存的数据各不相同,但是成员数据进行操作的成员函数的程序代码均是一样的,当对一个对象调用成员函数时,编译程序先将对象的地址赋给this指针,然后调用成员函数,每次成员函数存取数据成员时候,也隐含使用this指针。

10.1 基本原理

每个非静态成员函数都隐式包含一个 this 指针,指向调用该函数的对象.

  • obj.setx(888) 被调用时,编译器将 obj 的地址赋给 this
  • 成员函数内访问 x 等价于 this->x.
#include <iostream>  // c++ 包含头文件输入输出流
#include <iomanip>
using namespace std;

class CTest {
private:
    int x;
public:
    int getx() const {
        return x;
    }
    void setx(int x) {
        this->x = x;
        cout << "this指针存储的内存地址为:" << this << endl << endl;
    }
};

int main() {
    CTest obj;
    obj.setx(888);
    cout << "对象obj在内存的地址为:" << &obj << endl;
    cout << "对象obj所保存的值为多少:" << obj.getx() << endl;
    return 0;
}

C++之面向对象

this 的地址与 &obj 完全一致。

10.2 this 的主要用途

class CTest {
    int x;
public:
    // 1. 区分成员变量与形参同名的情况
    void setx(int x) {
        this->x = x;       // this->x = 成员变量, x = 形参
    }

    // 2. 返回对象自身的引用(链式调用)
    CTest& add(int val) {
        x += val;
        return *this;       // 返回当前对象
    }

    // 3. 比较是否同一个对象
    bool isSame(const CTest& other) const {
        return this == &other;
    }
};

// 链式调用示例
CTest obj;
obj.add(10).add(20).add(30);

const 成员函数与 this

class CTest {
    int x;
public:
    int getx() const {     // const 成员函数
        return x;          // this 的类型是 const CTest*
    }
    // 在 const 函数中不能修改成员变量,不能调用非 const 成员函数
};

总结:

概念 说明
this 是什么 指向当前对象的指针,类型为类名* const
何时产生 非静态成员函数被调用时自动传入
主要用途 区分同名变量、返回自身引用、对象比较
const 函数中 this 变为 const 类名* const,不可修改成员

十一、静态成员

11.1 静态数据成员

通常,每当说明一个对象的时候,把该类中的有关成员数据拷贝到该对象中,即同一类不同对象,其成员数据之间是相互独立的。

#include <iostream>
using namespace std;

class CTest {
    int x, y;
public:
    void Setxy(int a, int b) {
        x = a;
        y = b;
    }
};

int main() {
    CTest a1, a2;
    a1.Setxy(1, 2);
    a1.Setxy(3, 4);
    return 0;
}

当将类的某一个数据成员的存储类型指定为静态类型时,则由该类所产生的所有对象,其静态成员均共享一个存储空间,这个空间是在编译的时候分配的,在说明对象的时候,并不为静态类型的成员分配空间。在定义中,用关键字static修饰的数据成员称为静态数据成员

说明:

  • 类的静态数据成员是静态分配存储空间的,当类中定义了静态数据成员时,在编译时,就要为类的静态数据成员分配存储空间。

  • 必须在文件作用域中,对静态数据成员做一次且只能做一次定义性说明。
    C++之面向对象

  • 在对静态数据成员定义性的说明的时候也可以指定一个初值。

  • 全局变量在程序中的任何位置都可以访问,静态数据成员受到访问权限的约束,必须是public权限时,才可能在类外进行访问。

  • 为了保持静态数据成员取值的一致性,通常在构造函数中不给静态数据成员置初值,而是在对静态数据成员的定义性说明时指定初值。

#include <iostream>
using namespace std;

class CTest {
public:
    CTest(int x = 0) {
        i = x;
        icount++;
    }
    void disp() {
        cout << "i=" << i << ",icount=" << icount << endl;
    }
private:
    int i;
    static int icount;
};

// 静态成员定义性说明
int CTest::icount = 0;

int main() {
    CTest obj1[100];
    obj1->disp();
    CTest obj2[5];
    obj2->disp();
    return 0;
}

在这里插入代码片

11.2 静态成员函数

可以将类的成员函数定义为静态的成员函数,使用关键字static来修饰成员函数。

    static void disp() {
       .....
    }
  • 与静态数据成员一样,在类的程序代码中,通过类名加上作用域操作符,可直接调用静态成员函数。
  • 静态成员函数只能直接使用本类的静态数据成员或静态成员函数,但不能直接使用非静态的数据成员(可以引用使用)
  • 不包含对象地址的this指向。
  • 静态成员函数的实现部分在类定义之外定义时,其前面不能加修饰符static,这是由于关键字static不是数据类型的组成部分,因此,在类外面定义静态成员函数的实现部分时,不能使用。
  • 不能把静态成员函数定义为虚函数。
  • 可将静态成员函数定义为内敛的inline,其定义方法与非静态成员函数完全相同。
#include <iostream>
using namespace std;

class CTest {
public:
    CTest(int a) {
        x = a;
        y = y + x;
    }
    static void disp(CTest obj) {
        cout << "x=" << obj.x << ",y=" << y << endl;
    }
private:
    int x;
    static int y;
};

int CTest::y = 10;

int main() {
    CTest obj1(5), obj2(10);
    CTest::disp(obj1);
    CTest::disp(obj2);
    return 0;
}

十二、指向类的指针

一个指向C++类的指针与指向结构的指针类似,访问指向类的指针的成员需要使用->,就像访问指向结构的指针一样。与所有的指针一样,必须使用指针之前,对指针进行初始化。

#include <iostream>
using namespace std;

class CTest {
public:
    CTest(int a) {
        x = a;
        y = y + x;
    }
    static void disp(CTest obj) {
        cout << "x=" << obj.x << ",y=" << y << endl;
    }
private:
    int x;
    static int y;
};

int CTest::y = 10;

int main() {
    CTest obj1(5), obj2(10);
    CTest::disp(obj1);
    CTest::disp(obj2);

    CTest* pobj;
    pobj = &obj1;
    pobj->disp(obj1);

    return 0;
}

C++之面向对象


知行合一,拒绝不动手的空想~

更多推荐