C/C++ Linux 开发教程(GCC + CMake) / C++ 基础(类与对象)
C++ 基础(类与对象)
1. 类定义(class vs struct)
在 C++ 中,class 和 struct 都可以用来定义类,唯一的默认区别是访问权限。
| 关键字 | 默认访问权限 | 默认继承方式 |
|---|---|---|
class | private | private |
struct | public | public |
// 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&) const | C++20 可用 <=> |
<< | ostream& operator<<(ostream&, const T&) | 非成员友元 |
[] | T& operator[](size_t) | 成员函数 |
() | R operator()(args...) | 函数对象(仿函数) |
9. 类与结构体区别
| 特性 | class | struct |
|---|---|---|
| 默认访问权限 | private | public |
| 默认继承方式 | private | public |
| 可以有成员函数 | ✅ | ✅ |
| 可以有构造/析构 | ✅ | ✅ |
| 可以用模板 | ✅ | ✅ |
| 聚合初始化 | 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;
}
扩展阅读
- cppreference — Classes
- cppreference — Access specifiers
- C++ Core Guidelines — C.1-C.135 (Class hierarchy)
- Effective C++ 条款 19-26(类设计)