在C++中,类(class)是面向对象编程的核心概念,它允许我们将数据(成员变量)和操作这些数据的函数(成员函数)封装在一起。让我们通过一个更完整的Date类示例来理解类的定义:
cpp复制class Date {
public:
// 构造函数
Date(int year, int month, int day) {
setYear(year);
setMonth(month);
setDay(day);
}
// 成员函数(方法)
void display() const {
std::cout << _year << "-" << _month << "-" << _day << std::endl;
}
// 设置器(setters)
void setYear(int year) {
if (year > 0) _year = year;
}
void setMonth(int month) {
if (month >= 1 && month <= 12) _month = month;
}
void setDay(int day) {
if (day >= 1 && day <= 31) _day = day;
}
// 获取器(getters)
int getYear() const { return _year; }
int getMonth() const { return _month; }
int getDay() const { return _day; }
private:
// 成员变量(属性)
int _year;
int _month;
int _day;
};
注意:在实际开发中,建议将类的声明和实现分离,头文件(.h)放声明,源文件(.cpp)放实现,这样可以提高编译效率和代码可维护性。
关于class和struct的区别,虽然C++中两者都可以用来定义类,但存在以下关键差异:
C++提供了三种访问限定符来控制类成员的可见性:
访问控制的实际应用原则:
cpp复制class BankAccount {
public:
// 公共接口
void deposit(double amount) {
if (amount > 0) _balance += amount;
}
double getBalance() const {
return _balance;
}
protected:
// 子类可以访问
void logTransaction(const std::string& type, double amount) {
_transactionHistory.push_back({type, amount});
}
private:
// 私有数据
double _balance;
std::vector<std::pair<std::string, double>> _transactionHistory;
};
类定义了一个独立的作用域,所有成员都在这个作用域内。在类外定义成员函数时,需要使用作用域解析运算符(::)来指明函数属于哪个类:
cpp复制// 头文件 Date.h
class Date {
public:
void display() const;
// 其他成员声明...
};
// 源文件 Date.cpp
#include "Date.h"
void Date::display() const {
std::cout << _year << "-" << _month << "-" << _day;
}
类作用域的几个特点:
类实例化(创建对象)的几种方式:
cpp复制// 栈上分配
Date d1(2023, 1, 1); // 直接初始化
Date d2 = Date(2023, 1, 1); // 拷贝初始化
Date d3{2023, 1, 1}; // 列表初始化(C++11)
// 堆上分配
Date* pd1 = new Date(2023, 1, 1);
Date* pd2 = new Date{2023, 1, 1};
// 动态数组
Date* dates = new Date[3]{{2023,1,1}, {2023,2,1}, {2023,3,1}};
对象生命周期管理要点:
对象在内存中只存储非静态成员变量,成员函数和静态成员不占用对象空间。计算对象大小时需要考虑内存对齐规则:
cpp复制class Example {
char c; // 1字节
int i; // 4字节
double d; // 8字节
short s; // 2字节
};
// 在64位系统上,sizeof(Example)不是简单的1+4+8+2=15
// 实际大小需要考虑对齐:
// 假设默认对齐为8字节:
// char c (1) + 3填充 + int i (4) + double d (8) + short s (2) + 6填充 = 24字节
内存对齐规则的实际应用:
即使类没有任何成员,它的对象大小也不为零:
cpp复制class Empty {};
// sizeof(Empty) == 1 (编译器会插入一个占位字节)
// 单例模式示例
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
// 删除拷贝构造函数和赋值运算符
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
~Singleton() = default;
};
this指针是编译器自动添加的隐式参数,它指向调用成员函数的对象实例。从底层看:
cpp复制// 源代码
class MyClass {
public:
void setValue(int v) { value = v; }
private:
int value;
};
// 编译器处理后
struct MyClass {
int value;
};
void MyClass_setValue(MyClass* this, int v) { this->value = v; }
this指针的特点:
cpp复制class Calculator {
public:
Calculator& add(int x) { value += x; return *this; }
Calculator& sub(int x) { value -= x; return *this; }
int get() const { return value; }
private:
int value = 0;
};
// 使用
int result = Calculator().add(5).sub(3).add(10).get();
cpp复制class Person {
public:
void setName(std::string name) {
this->name = name; // 明确指定成员变量
}
private:
std::string name;
};
cpp复制class Buffer {
public:
Buffer* getThis() { return this; }
};
const成员函数中的this指针也是const的,这保证了不会通过this修改对象状态:
cpp复制class ConstDemo {
public:
void modify() { x = 42; } // 普通成员函数
void inspect() const { x = 42; }// 错误!不能修改成员
void print() const {
// 在const成员函数中,this的类型是const ConstDemo*
std::cout << x;
}
private:
int x;
};
const正确性原则:
静态成员属于类而不是对象,所有对象共享同一份静态成员:
cpp复制class Counter {
public:
Counter() { ++count; }
~Counter() { --count; }
static int getCount() { return count; }
private:
static int count; // 声明
};
int Counter::count = 0; // 定义并初始化
// 使用
Counter c1, c2;
std::cout << Counter::getCount(); // 输出2
静态成员的特点:
友元机制允许特定函数或类访问私有成员:
cpp复制class PrivateData {
friend class FriendClass;
friend void friendFunction(const PrivateData&);
private:
int secret = 42;
};
class FriendClass {
public:
void peek(const PrivateData& pd) {
std::cout << pd.secret; // 可以访问私有成员
}
};
void friendFunction(const PrivateData& pd) {
std::cout << pd.secret; // 可以访问私有成员
}
友元使用建议:
在需要不完全类型时可以使用前向声明:
cpp复制class B; // 前向声明
class A {
public:
void useB(B* b);
};
class B {
// B的实现
};
void A::useB(B* b) {
// 实现
}
前向声明的适用场景:
忘记分号:类定义结束后必须有分号
cpp复制class Error {} // 错误,缺少分号
重复定义:同一作用域中类名不能重复
cpp复制class A {};
class A {}; // 错误
访问违规:尝试访问私有成员
cpp复制class C { int x; };
C c; c.x = 5; // 错误
未实现的纯虚函数(抽象类)
cpp复制class Abstract {
public:
virtual void mustImplement() = 0;
};
Abstract a; // 错误,不能实例化抽象类
查看对象布局:
-fdump-class-hierarchy选项/d1reportSingleClassLayoutXXX检查对象大小:
cpp复制std::cout << "Size: " << sizeof(MyClass) << std::endl;
内存查看工具:
this指针调试:
对象布局优化:
内联小函数:
避免不必要的拷贝:
静态多态:
在实际项目中,我发现合理使用const和引用可以避免很多潜在问题。例如,对于不会修改对象状态的成员函数都应该声明为const,这样const对象也能调用这些函数。同时,参数传递时优先考虑const引用,特别是对于自定义类类型,这既能避免拷贝开销又能防止意外修改。