强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

C/C++ Linux 开发教程(GCC + CMake) / C++ 基础(类与对象)

C++ 基础(类与对象)

1. 类定义(class vs struct)

在 C++ 中,classstruct 都可以用来定义类,唯一的默认区别是访问权限。

关键字默认访问权限默认继承方式
classprivateprivate
structpublicpublic
// class 默认 private
class MyClass {
    int x;  // private by default
};

// struct 默认 public
struct MyStruct {
    int x;  // public by default
};

int main() {
    MyStruct s;
    s.x = 10;  // OK

    // MyClass m;
    // m.x = 10;  // Error: x is private

    return 0;
}

编译运行:

g++ -std=c++17 -o demo demo.cpp && ./demo

💡 提示:习惯上,纯数据聚合类型用 struct,有行为(成员函数)的类型用 class


2. 成员变量与成员函数

#include <iostream>
#include <string>

class Student {
public:
    // 成员变量
    std::string name;
    int age;
    double score;

    // 成员函数
    void introduce() const {
        std::cout << "我叫 " << name
                  << ",今年 " << age << " 岁"
                  << ",成绩 " << score << " 分\n";
    }

    // 带返回值的成员函数
    bool isPassed() const {
        return score >= 60.0;
    }
};

int main() {
    Student s{"张三", 20, 85.5};
    s.introduce();

    if (s.isPassed()) {
        std::cout << s.name << " 及格了\n";
    }

    return 0;
}

3. 访问控制(public / private / protected)

访问级别类内派生类类外
public
protected
private
#include <iostream>
#include <string>

class BankAccount {
private:
    std::string owner_;
    double balance_;

protected:
    void log(const std::string& msg) const {
        std::cout << "[LOG] " << owner_ << ": " << msg << "\n";
    }

public:
    BankAccount(const std::string& owner, double initial)
        : owner_(owner), balance_(initial) {}

    void deposit(double amount) {
        if (amount > 0) {
            balance_ += amount;
            log("存入 " + std::to_string(amount));
        }
    }

    void withdraw(double amount) {
        if (amount > 0 && amount <= balance_) {
            balance_ -= amount;
            log("取出 " + std::to_string(amount));
        }
    }

    double getBalance() const { return balance_; }
};

int main() {
    BankAccount acc("李四", 1000.0);
    acc.deposit(500);
    acc.withdraw(200);
    std::cout << "余额: " << acc.getBalance() << "\n";

    // acc.balance_ = 999999;  // Error: private
    // acc.log("test");        // Error: protected

    return 0;
}

4. this 指针

this 是一个隐含的指针,指向调用成员函数的对象本身。

#include <iostream>

class Point {
    int x_, y_;

public:
    Point(int x, int y) : x_(x), y_(y) {}

    // this 指针用于区分同名参数和成员变量
    Point& setX(int x) {
        this->x_ = x;
        return *this;  // 返回自身引用,支持链式调用
    }

    Point& setY(int y) {
        this->y_ = y;
        return *this;
    }

    void print() const {
        std::cout << "(" << x_ << ", " << y_ << ")\n";
    }

    // this 用于比较对象自身
    bool isSameAs(const Point& other) const {
        return this == &other;
    }
};

int main() {
    Point p(0, 0);
    p.setX(3).setY(4);  // 链式调用
    p.print();           // (3, 4)

    Point q(3, 4);
    std::cout << "p == q? " << std::boolalpha << p.isSameAs(q) << "\n"; // false
    std::cout << "p == p? " << p.isSameAs(p) << "\n";                   // true

    return 0;
}

💡 提示return *this 是实现链式调用(builder 模式)的核心技巧。


5. 友元函数(friend)

friend 允许非成员函数或其他类访问 private / protected 成员。

#include <iostream>

class Rectangle {
    double width_, height_;

public:
    Rectangle(double w, double h) : width_(w), height_(h) {}

    // 友元函数:可以访问 private 成员
    friend double area(const Rectangle& r);

    // 友元类
    friend class Printer;
};

// 非成员函数,但能访问 private 成员
double area(const Rectangle& r) {
    return r.width_ * r.height_;
}

class Printer {
public:
    static void print(const Rectangle& r) {
        // 可以访问 private 成员
        std::cout << "Rectangle(" << r.width_ << " x " << r.height_ << ")\n";
    }
};

int main() {
    Rectangle rect(5.0, 3.0);
    std::cout << "面积: " << area(rect) << "\n";
    Printer::print(rect);

    return 0;
}

⚠️ 注意:友元破坏了封装性,应谨慎使用。常见于运算符重载(如 operator<<)。


6. 静态成员(static)

特性静态成员变量静态成员函数
属于类而非对象
可用 ClassName:: 访问
可访问 this 指针
可访问非静态成员
#include <iostream>
#include <string>

class Logger {
    // 静态成员变量(C++17 inline 可在类内定义)
    inline static int instanceCount_ = 0;
    inline static int totalMessages_ = 0;
    std::string tag_;

public:
    Logger(const std::string& tag) : tag_(tag) {
        ++instanceCount_;
    }

    ~Logger() {
        --instanceCount_;
    }

    void log(const std::string& msg) {
        std::cout << "[" << tag_ << "] " << msg << "\n";
        ++totalMessages_;
    }

    // 静态成员函数
    static int getInstanceCount() { return instanceCount_; }
    static int getTotalMessages() { return totalMessages_; }
};

int main() {
    std::cout << "实例数: " << Logger::getInstanceCount() << "\n";

    {
        Logger a("APP");
        Logger b("DB");
        a.log("启动");
        b.log("连接成功");
        std::cout << "实例数: " << Logger::getInstanceCount() << "\n";   // 2
        std::cout << "消息数: " << Logger::getTotalMessages() << "\n";   // 2
    }

    std::cout << "实例数: " << Logger::getInstanceCount() << "\n";       // 0
    return 0;
}

7. 内联成员函数

在类内定义的成员函数默认为 inline。也可在类外用 inline 关键字标记。

#include <iostream>

class MathUtils {
public:
    // 类内定义 → 自动 inline
    static int abs(int x) {
        return x < 0 ? -x : x;
    }

    // 声明
    static double clamp(double value, double lo, double hi);
};

// 类外定义为 inline
inline double MathUtils::clamp(double value, double lo, double hi) {
    if (value < lo) return lo;
    if (value > hi) return hi;
    return value;
}

int main() {
    std::cout << MathUtils::abs(-42) << "\n";           // 42
    std::cout << MathUtils::clamp(3.5, 0.0, 1.0) << "\n"; // 1.0
    return 0;
}

💡 提示inline 只是对编译器的建议,现代编译器会自行判断是否内联展开。inline 更重要的作用是允许多个编译单元中包含同一定义(避免 ODR 违规)。


8. 运算符重载(operator=)

#include <iostream>
#include <cstring>

class MyString {
    char* data_;
    size_t size_;

public:
    // 构造函数
    MyString(const char* str = "") : size_(std::strlen(str)) {
        data_ = new char[size_ + 1];
        std::strcpy(data_, str);
    }

    // 拷贝构造函数
    MyString(const MyString& other) : size_(other.size_) {
        data_ = new char[size_ + 1];
        std::strcpy(data_, other.data_);
    }

    // 拷贝赋值运算符(copy-and-swap 惯用法)
    MyString& operator=(MyString other) {
        std::swap(data_, other.data_);
        std::swap(size_, other.size_);
        return *this;
    }

    // 析构函数
    ~MyString() { delete[] data_; }

    // operator+ (拼接)
    MyString operator+(const MyString& rhs) const {
        MyString result;
        delete[] result.data_;
        result.size_ = size_ + rhs.size_;
        result.data_ = new char[result.size_ + 1];
        std::strcpy(result.data_, data_);
        std::strcat(result.data_, rhs.data_);
        return result;
    }

    // operator== (相等比较)
    bool operator==(const MyString& rhs) const {
        return std::strcmp(data_, rhs.data_) == 0;
    }

    // operator<< (输出,声明为友元)
    friend std::ostream& operator<<(std::ostream& os, const MyString& s) {
        return os << s.data_;
    }

    // operator[] (下标访问)
    char& operator[](size_t index) { return data_[index]; }
    const char& operator[](size_t index) const { return data_[index]; }

    size_t size() const { return size_; }
};

int main() {
    MyString a("Hello");
    MyString b(" World");
    MyString c = a + b;
    std::cout << c << "\n";           // Hello World
    std::cout << "长度: " << c.size() << "\n";

    MyString d;
    d = a;  // operator=
    std::cout << std::boolalpha;
    std::cout << (d == a) << "\n";    // true
    std::cout << (a == b) << "\n";    // false

    c[0] = 'h';
    std::cout << c << "\n";           // hello World

    return 0;
}

常用可重载运算符一览:

运算符典型签名建议
=T& operator=(T)成员函数,copy-and-swap
+ / -T operator+(const T&) const成员或非成员
== / !=bool operator==(const T&) constC++20 可用 <=>
<<ostream& operator<<(ostream&, const T&)非成员友元
[]T& operator[](size_t)成员函数
()R operator()(args...)函数对象(仿函数)

9. 类与结构体区别

特性classstruct
默认访问权限privatepublic
默认继承方式privatepublic
可以有成员函数
可以有构造/析构
可以用模板
聚合初始化C++11 起放宽天然支持

实际使用约定:

// ✅ 用 struct —— 纯数据聚合
struct Color {
    uint8_t r, g, b, a;
};

// ✅ 用 class —— 有行为和封装
class Image {
    int width_, height_;
    std::vector<Color> pixels_;

public:
    Image(int w, int h) : width_(w), height_(h), pixels_(w * h) {}
    Color& pixel(int x, int y) { return pixels_[y * width_ + x]; }
};

10. 命名空间(namespace)

#include <iostream>

namespace graphics {
    class Point {
    public:
        double x, y;
        Point(double x, double y) : x(x), y(y) {}
    };

    double distance(const Point& a, const Point& b) {
        double dx = a.x - b.x;
        double dy = a.y - b.y;
        return std::sqrt(dx * dx + dy * dy);
    }

    // 嵌套命名空间 (C++17 简化写法)
    namespace transform {
        Point translate(const Point& p, double dx, double dy) {
            return {p.x + dx, p.y + dy};
        }
    }
}

// 匿名命名空间 —— 限制在当前编译单元内可见
namespace {
    int internalCounter = 0;
    void helperFunction() { ++internalCounter; }
}

int main() {
    using graphics::Point;

    Point a(0, 0);
    Point b(3, 4);
    std::cout << "距离: " << graphics::distance(a, b) << "\n"; // 5

    auto c = graphics::transform::translate(a, 10, 20);
    std::cout << "平移后: (" << c.x << ", " << c.y << ")\n";   // (10, 20)

    helperFunction();
    // internalCounter 仅在本文件可见

    return 0;
}
namespace 特性说明
嵌套namespace A::B::C {} (C++17)
别名namespace fs = std::filesystem;
匿名namespace { ... } 等价于 static
using 声明using std::cout; 引入单个名称
using 指令using namespace std; 引入全部(不推荐在头文件中使用)

⚠️ 注意:永远不要在头文件中使用 using namespace std;,这会污染所有包含该头文件的翻译单元。


实际场景:设计一个简易栈

#include <iostream>
#include <stdexcept>
#include <vector>

template <typename T>
class Stack {
    std::vector<T> data_;

public:
    void push(const T& value) { data_.push_back(value); }
    void push(T&& value) { data_.push_back(std::move(value)); }

    T pop() {
        if (data_.empty()) {
            throw std::underflow_error("Stack is empty");
        }
        T value = std::move(data_.back());
        data_.pop_back();
        return value;
    }

    const T& top() const {
        if (data_.empty()) {
            throw std::underflow_error("Stack is empty");
        }
        return data_.back();
    }

    bool empty() const { return data_.empty(); }
    size_t size() const { return data_.size(); }
};

int main() {
    Stack<int> stk;
    stk.push(10);
    stk.push(20);
    stk.push(30);

    std::cout << "栈顶: " << stk.top() << "\n";   // 30
    std::cout << "弹出: " << stk.pop() << "\n";    // 30
    std::cout << "弹出: " << stk.pop() << "\n";    // 20
    std::cout << "大小: " << stk.size() << "\n";   // 1

    return 0;
}

扩展阅读