作为一名从C语言转型到C++的程序员,我深刻体会到类和对象带来的思维转变。C语言是面向过程的编程范式,而C++引入了面向对象的概念,这不仅仅是语法上的变化,更是编程思维方式的革新。
在C语言中,我们通过结构体(struct)来组织数据,通过函数来操作这些数据。数据和操作是分离的,这导致代码的组织性和封装性较差。而C++中的类(class)将数据(成员变量)和操作数据的方法(成员函数)封装在一起,形成了更高级别的抽象。
重要提示:C++完全兼容C语言的struct语法,但在C++中struct默认会被升级为class,这意味着在C++中struct也可以包含成员函数,并且具有访问控制特性。
C++中定义类的基本语法如下:
cpp复制class ClassName {
// 访问限定符
public:
// 公有成员(变量和函数)
private:
// 私有成员(变量和函数)
protected:
// 保护成员(变量和函数)
}; // 注意这里的分号不能省略
让我们通过一个更完整的栈(Stack)实现来理解类的定义:
cpp复制class Stack {
public:
// 构造函数
Stack(int initialCapacity = 4) {
arr = new int[initialCapacity];
if (arr == nullptr) {
throw std::bad_alloc();
}
capacity = initialCapacity;
top = 0;
}
// 析构函数
~Stack() {
delete[] arr;
}
// 成员函数
void push(int value) {
if (top == capacity) {
resize(capacity * 2);
}
arr[top++] = value;
}
int pop() {
if (top == 0) {
throw std::out_of_range("Stack is empty");
}
return arr[--top];
}
bool isEmpty() const {
return top == 0;
}
private:
// 成员变量
int* arr;
int top;
int capacity;
// 私有辅助函数
void resize(int newCapacity) {
int* newArr = new int[newCapacity];
for (int i = 0; i < top; ++i) {
newArr[i] = arr[i];
}
delete[] arr;
arr = newArr;
capacity = newCapacity;
}
};
默认访问权限:
继承时的默认访问权限:
模板参数:
实际经验:在C++中,struct和class的差别越来越小。通常我们使用struct来表示简单的数据聚合,而使用class来表示更复杂的、有行为的对象。
C++提供了三种访问限定符来控制类成员的访问权限:
cpp复制class AccessExample {
public:
int publicVar; // 任何地方都可以访问
void publicMethod() {
// 可以访问所有成员
privateVar = 10;
protectedVar = 20;
}
protected:
int protectedVar; // 只有派生类和本类可以访问
void protectedMethod() {
// 可以访问所有成员
privateVar = 30;
}
private:
int privateVar; // 只有本类可以访问
void privateMethod() {
// 可以访问所有成员
protectedVar = 40;
}
};
在实际开发中,我总结了以下封装经验:
cpp复制class BankAccount {
public:
// 构造函数
BankAccount(double initialBalance) : balance(initialBalance) {}
// 存款
void deposit(double amount) {
if (amount <= 0) {
throw std::invalid_argument("Amount must be positive");
}
balance += amount;
}
// 取款
void withdraw(double amount) {
if (amount <= 0) {
throw std::invalid_argument("Amount must be positive");
}
if (amount > balance) {
throw std::runtime_error("Insufficient funds");
}
balance -= amount;
}
// 查询余额
double getBalance() const {
return balance;
}
private:
double balance; // 余额设为私有,防止外部直接修改
};
成员函数可以在类内部定义,也可以在类外部定义:
cpp复制class Rectangle {
public:
// 在类内部定义的成员函数(隐式内联)
double area() const {
return width * height;
}
// 声明但不定义
void setDimensions(double w, double h);
private:
double width;
double height;
};
// 在类外部定义的成员函数
void Rectangle::setDimensions(double w, double h) {
if (w <= 0 || h <= 0) {
throw std::invalid_argument("Dimensions must be positive");
}
width = w;
height = h;
}
在类内部定义的成员函数默认是内联的,这意味着:
注意事项:过度使用内联函数会导致代码膨胀,反而可能降低性能。通常只有简单的getter/setter和非常小的函数才适合内联。
cpp复制class ScopeExample {
public:
void method1();
void method2();
};
void ScopeExample::method1() {
// 可以访问所有类成员
method2();
}
void externalFunction() {
ScopeExample obj;
obj.method1(); // 通过对象访问
// method1(); // 错误:不在类作用域内
}
在C++社区中,常见的命名约定包括:
_size 或 size_MAX_SIZEMyClassgetSize() 或 get_size()我个人偏好以下划线开头的成员变量命名方式,因为它:
cpp复制class NamingExample {
public:
NamingExample(int size) : _size(size) {
_data = new int[_size];
}
~NamingExample() {
delete[] _data;
}
int getSize() const { return _size; }
private:
int _size;
int* _data;
};
在C++中,创建类对象有几种不同的语法:
cpp复制class MyClass {
public:
MyClass() { std::cout << "Constructor called\n"; }
~MyClass() { std::cout << "Destructor called\n"; }
};
int main() {
// 方式1:直接声明
MyClass obj1; // 栈上分配,自动管理生命周期
// 方式2:动态分配
MyClass* obj2 = new MyClass(); // 堆上分配,需要手动delete
// 方式3:使用智能指针(C++11起)
std::unique_ptr<MyClass> obj3(new MyClass()); // 自动管理内存
delete obj2; // 必须手动释放
return 0;
}
理解对象的内存布局对于编写高效代码很重要:
cpp复制class MemoryLayout {
char a; // 1字节
int b; // 通常4字节
double c; // 通常8字节
char d; // 1字节
};
// 由于内存对齐,这个类的大小可能大于1+4+8+1=14字节
// 在64位系统上,通常是24字节(1+3填充+4+8+1+7填充)
性能提示:合理安排成员变量的声明顺序可以减少内存浪费。通常将相同类型或大小相近的变量放在一起,可以减少填充字节。
忘记分号:类定义结束后必须加分号
cpp复制class ErrorExample {
// 成员声明
} // 错误:缺少分号
循环依赖:两个类互相引用
cpp复制class A {
B b; // 错误:B尚未定义
};
class B {
A a;
};
解决方案:使用前向声明和指针
cpp复制class B; // 前向声明
class A {
B* b; // 使用指针
};
class B {
A a;
};
访问权限错误:尝试访问私有成员
cpp复制class AccessError {
int secret;
public:
void reveal() { return secret; }
};
int main() {
AccessError obj;
// int s = obj.secret; // 错误:secret是私有的
int s = obj.reveal(); // 正确:通过公有方法访问
}
使用const成员函数:标记不修改对象的函数,可以在const对象上调用
cpp复制class DebugExample {
int count;
public:
int getCount() const { return count; } // const成员函数
void increment() { ++count; } // 非const成员函数
};
void func(const DebugExample& obj) {
// obj.increment(); // 错误:不能在const对象上调用非const方法
int c = obj.getCount(); // 正确
}
打印对象状态:重载<<运算符便于调试
cpp复制#include <iostream>
class Printable {
int data;
public:
Printable(int d) : data(d) {}
friend std::ostream& operator<<(std::ostream& os, const Printable& obj) {
return os << "Printable(" << obj.data << ")";
}
};
int main() {
Printable p(42);
std::cout << p << std::endl; // 输出: Printable(42)
}
使用静态断言检查类型属性:
cpp复制#include <type_traits>
class NonCopyable {
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
public:
NonCopyable() = default;
};
static_assert(!std::is_copy_constructible<NonCopyable>::value,
"NonCopyable should not be copyable");
让我们通过一个具体的例子,展示如何将C风格的结构体逐步演进为C++的类:
c复制// point.h
typedef struct Point {
double x;
double y;
} Point;
double point_distance(const Point* p1, const Point* p2);
void point_translate(Point* p, double dx, double dy);
c复制// point.c
#include <math.h>
#include "point.h"
double point_distance(const Point* p1, const Point* p2) {
double dx = p1->x - p2->x;
double dy = p1->y - p2->y;
return sqrt(dx*dx + dy*dy);
}
void point_translate(Point* p, double dx, double dy) {
p->x += dx;
p->y += dy;
}
cpp复制// point.hpp
class Point {
public:
double x;
double y;
double distance(const Point& other) const;
void translate(double dx, double dy);
};
cpp复制// point.cpp
#include <cmath>
#include "point.hpp"
double Point::distance(const Point& other) const {
double dx = x - other.x;
double dy = y - other.y;
return std::sqrt(dx*dx + dy*dy);
}
void Point::translate(double dx, double dy) {
x += dx;
y += dy;
}
cpp复制// point.hpp
class Point {
private:
double _x;
double _y;
public:
Point(double x = 0.0, double y = 0.0) : _x(x), _y(y) {}
// 获取坐标
double x() const { return _x; }
double y() const { return _y; }
// 设置坐标
void setX(double x) { _x = x; }
void setY(double y) { _y = y; }
// 操作
double distance(const Point& other) const;
void translate(double dx, double dy);
// 运算符重载
Point operator+(const Point& other) const;
Point& operator+=(const Point& other);
// 友元函数
friend std::ostream& operator<<(std::ostream& os, const Point& p);
};
cpp复制// point.cpp
#include <cmath>
#include <ostream>
#include "point.hpp"
double Point::distance(const Point& other) const {
double dx = _x - other._x;
double dy = _y - other._y;
return std::sqrt(dx*dx + dy*dy);
}
void Point::translate(double dx, double dy) {
_x += dx;
_y += dy;
}
Point Point::operator+(const Point& other) const {
return Point(_x + other._x, _y + other._y);
}
Point& Point::operator+=(const Point& other) {
_x += other._x;
_y += other._y;
return *this;
}
std::ostream& operator<<(std::ostream& os, const Point& p) {
return os << "Point(" << p._x << ", " << p._y << ")";
}
这个演进过程展示了从C的过程式编程到C++面向对象编程的转变,体现了封装、数据隐藏和接口设计等面向对象的核心概念。