C++程序设计语言笔记——引言:第一章 致读者

第一版前言

C++里最关键的概念是类。一个类就是一个用户定义类型。类提供了对数据的隐藏,数据的初始化保证,用户定义类型的隐式类型转换,动态类型识别,用户控制的存储管理,以及重载运算符的机制等。在类型检查和表述模块性方面,C++提供了比C好得多的功能。它还包含了许多并不直接与类相关的改进,包括符号常量、函数的在线替换、默认函数参数、重载函数名、自由存储管理运算符,以及引用类型等。C++保持了C高效处理硬件基本对象(位、字节、字、地址等)的能力。这就使用户定义类型能够在相当高的效率水平上实现。

第一章 致读者

0 C++的设计:

程序设计风格;类型检查;C 兼容性;语言、库和系统

1 C++的设计理念

将内置操作和内置类型直接映射到硬件,从而提供高效的内存利用和高效的底层操作;
灵活且低开销的抽象机制,使得用户自定义类型无论是符号表达、使用范围还是性能都能与内置类型相当。

2 C++的设计理念和设计目标:

1.不给比 C++更底层的语言留任何余地(在极少的情况下汇编语言是例外)。因为,如果你能用一种更底层的语言编写出更高效的代码,那意味着这种语言很可能比 C++更适合系统程序设计。
2.你不使用它,就不要为它付出代价。如果程序员能够手工编写出很不错的代码,来模拟一个语言特性或是一个基础的抽象机制,甚至性能更好一些,那么一些人就真的会去编写这种代码,而很多人就会效仿。因此,与等价的替代方法相比,我们设计的语言特性或是基础的抽象机制必须不浪费哪怕一个字节或是一个处理器时钟周期。这就是众所周知的零开销原则(zero-overhead principle)。

3 软件设计和编程的基本理念:

1.用代码直接表达想法;
2.无关的想法应独立表达;
3.用代码直接描述想法之间的关联;
4.可以自由的组合用代码表达的想法,但仅在这种组合有意义时;
5.简单的想法应简单表达。

4 C++语言特性直接支持四种程序设计风格:

  • 1.过程式程序设计:
    这种风格专注于处理和设计恰当的数据结构。支持这种风格也是 C 语言(以及 Algol、Fortran 和很多其他语言)的设计目标。C++对这种风格的支持体现为内置类型、运算符、语句、函数、struct 和 union 等特性。除少数例外,C 可以看作 C++的子集。与 C 相比,C++对过程式程序设计的支持更强,这体现在很多额外的语言特性和一个更严格、更灵活且对过程式编程支持更好的类型系统。
  • 2.数据抽象:
    这种风格专注于接口的设计以及一般实现细节的隐藏和特殊的表示方式。C++支持具体类和抽象类。一些语言特性可直接用来定义具有私有实现细节、构造函数和析构函数以及相关操作的类。而抽象类则为完全的数据隐藏提供了直接支持。
    3.面向对象程序设计:
    这种风格专注于类层次的设计、实现和使用。除了允许定义类框架之外,C++还提供了各种各样的特性来支持类框架中的导航以及简化由已有的类来定义新的类。类层次提供了运行时多态和封装机制。
  • 4.泛型程序设计:
    这种风格专注于通用算法的设计、实现和使用。在这里,“通用”的含义是,一个算法可以设计成能处理多种类型,只要这些类型满足算法对其实参的要求即可。C++支持泛型编程的主要特性是模板。模板提供了(运行时)参数多态。
    上述这些设计和编程风格的强大在于它们的综合,每种风格都对综合起到了重要作用,而这种综合实际上就是 C++。因此,只关注一种风格是错误的:除非你只编写一些玩具程序,否则只关注一种风格会导致开发工作的浪费,产生非最优的(不灵活的、冗长的、性能低下的、不易维护的,等等)程序。
  • 组合使用这些不同风格的例子以及支持这种组合的语言特性:
    1.类支持上述所有风格;这依赖于用户如何将想法表示为用户自定义类型或是自定义类型的对象。
    2.公有/私有访问控制支持数据抽象和面向对象程序设计一清晰地分离接口和实现。
    3.成员函数、构造函数、析构函数以及用户自定义赋值运算符为对象提供了一个清晰的功能接口,这是数据抽象和面向对象程序设计所需要的。这些特性还提供了一种统一的符号表示,这是泛型编程所需要的。更一般的重载机制直到 1984 年才产生,而一致初始化机制直到 2010 年才出现。
    4.函数声明为成员函数和独立函数提供了特殊的具备静态检查的接口,因此支持上述所有程序设计风格,而这也是重载所需要的。当时,C 还没有“函数原型”,但 Simula 已经有了函数声明和成员函数。
    5.泛型函数和参数化类型(当时是使用宏从函数和类生成的)支持泛型程序设计。模板直到 1988 年才产生。基类和派生类为面向对象程序设计和某些形式的数据抽象提供了基础。虚丽数直到1983 年才产生。
    6.内联使得在进行系统程序设计以及构造运行时间和空间都很高效的库时,使用上述语言特性的代价可以接受。

以下是针对C++支持的四种程序设计风格的具体例子,展示了如何组合使用这些风格来编写高效、灵活且易于维护的程序:


1. 过程式程序设计

例子:

#include 

// 过程式程序设计:专注于函数和数据结构
struct Point {
    int x;
    int y;
};

void printPoint(const Point& p) {
    std::cout << "(" << p.x << ", " << p.y << ")" << std::endl;
}

int main() {
    Point p = {10, 20};
    printPoint(p);
    return 0;
}
  • 解释:使用struct定义数据结构,通过函数操作数据,体现了过程式程序设计的风格。

2. 数据抽象

例子:

#include 

// 数据抽象:隐藏实现细节
class Point {
public:
    Point(int x, int y) : x(x), y(y) {}
    void print() const {
        std::cout << "(" << x << ", " << y << ")" << std::endl;
    }
private:
    int x;
    int y;
};

int main() {
    Point p(10, 20);
    p.print();
    return 0;
}
  • 解释:使用类封装数据,提供公共接口(如print方法),隐藏实现细节,体现了数据抽象的风格。

3. 面向对象程序设计

例子:

#include 

// 面向对象程序设计:类层次和多态
class Shape {
public:
    virtual void draw() const = 0;
    virtual ~Shape() = default;
};

class Circle : public Shape {
public:
    void draw() const override {
        std::cout << "Drawing a circle" << std::endl;
    }
};

class Square : public Shape {
public:
    void draw() const override {
        std::cout << "Drawing a square" << std::endl;
    }
};

int main() {
    Shape* shapes[] = {new Circle(), new Square()};
    for (const auto& shape : shapes) {
        shape->draw();
        delete shape;
    }
    return 0;
}
  • 解释:通过基类Shape和派生类CircleSquare实现多态,体现了面向对象程序设计的风格。

4. 泛型程序设计

例子:

#include 
#include 

// 泛型程序设计:通用算法
template <typename T>
void print(const std::vector<T>& vec) {
    for (const auto& item : vec) {
        std::cout << item << " ";
    }
    std::cout << std::endl;
}

int main() {
    std::vector<int> intVec = {1, 2, 3};
    std::vector<std::string> strVec = {"Hello", "World"};
    print(intVec);
    print(strVec);
    return 0;
}
  • 解释:使用模板实现通用算法print,支持多种类型,体现了泛型程序设计的风格。

组合使用不同风格的例子

1. 类支持所有风格

例子:

#include 
#include 

// 类支持过程式、数据抽象、面向对象和泛型程序设计
template <typename T>
class Container {
public:
    void add(const T& item) {
        data.push_back(item);
    }
    void print() const {
        for (const auto& item : data) {
            std::cout << item << " ";
        }
        std::cout << std::endl;
    }
private:
    std::vector<T> data;
};

int main() {
    Container<int> intContainer;
    intContainer.add(1);
    intContainer.add(2);
    intContainer.print();

    Container<std::string> strContainer;
    strContainer.add("Hello");
    strContainer.add("World");
    strContainer.print();
    return 0;
}
  • 解释Container类结合了数据抽象(封装std::vector)、泛型编程(模板)和面向对象(类方法)。

2. 公有/私有访问控制

例子:

class BankAccount {
public:
    BankAccount(double balance) : balance(balance) {}
    void deposit(double amount) {
        balance += amount;
    }
    double getBalance() const {
        return balance;
    }
private:
    double balance;
};
  • 解释:通过publicprivate分离接口和实现,支持数据抽象和面向对象程序设计。

3. 成员函数、构造函数、析构函数

例子:

class FileHandler {
public:
    FileHandler(const std::string& filename) : file(filename) {}
    ~FileHandler() {
        file.close();
    }
    void write(const std::string& content) {
        file << content;
    }
private:
    std::ofstream file;
};
  • 解释:构造函数和析构函数管理资源,成员函数提供接口,支持数据抽象和面向对象程序设计。

4. 函数声明和重载

例子:

void print(int value) {
    std::cout << "Integer: " << value << std::endl;
}

void print(double value) {
    std::cout << "Double: " << value << std::endl;
}

int main() {
    print(42);
    print(3.14);
    return 0;
}
  • 解释:函数重载支持多种类型,体现了泛型编程的思想。

5. 模板和类层次

例子:

template <typename T>
class Box {
public:
    Box(const T& value) : value(value) {}
    T getValue() const {
        return value;
    }
private:
    T value;
};

class Animal {
public:
    virtual void speak() const = 0;
};

class Dog : public Animal {
public:
    void speak() const override {
        std::cout << "Woof!" << std::endl;
    }
};
  • 解释:模板支持泛型编程,类层次支持面向对象程序设计。

6. 内联函数

例子:

inline int square(int x) {
    return x * x;
}

int main() {
    std::cout << square(5) << std::endl;
    return 0;
}
  • 解释:内联函数减少函数调用开销,适用于系统程序设计和高效库的实现。

5

C++中的基本对象具有唯一的身份,即它们位于内存中的定位置,而且可以通过比较地址与(可能)具有相同值的其他对象区分开来。表示这种对象的表达式被称为左值(Ivalue)。但早在 C++的祖先[Barron,1963]月所在的年代,就已经有了没有身份的对象(对于这类对象,不存在一个安全存储的地址可供随后使用)。在 C++11 中,这一右值(rvalue)的概念发展为一个新的概念–不能以低开销进行移动的值。以这种对象为基础的技术很像函数式程序设计中所用的技术(在函数式程序设计中,有身份的对象的概念是令人反感的)。这一新概念对泛型程序设计技术和语言特性(如 lambda 表达式)是很好的补充。它还很好地解决了与"简单抽象数据类型"相关的一些问题,例如,如何从一个操作(如矩阵+)优雅而高效地返回一个大矩阵。

6 如何用C++编写出好的程序:

下面是一些例子,展示如何用C++编写出好的程序:

1. 用代码直接表达想法

例子:

// 直接表达“计算两个数的和”的想法
int add(int a, int b) {
    return a + b;
}

在这个例子中,代码直接表达了“计算两个数的和”的想法,函数名add和操作a + b都非常直观。

2. 用代码直接表达想法之间的关联

例子:

// 使用类层次结构表达“形状”和“圆形”之间的关联
class Shape {
public:
    virtual double area() const = 0;
};

class Circle : public Shape {
public:
    Circle(double r) : radius(r) {}
    double area() const override {
        return 3.14159 * radius * radius;
    }
private:
    double radius;
};

这里,Circle类继承自Shape类,表达了“圆形是一种形状”的层次关系。

3. 无关的想法独立用代码表达

例子:

// 独立的函数表达不同的想法
void printHello() {
    std::cout << "Hello, World!" << std::endl;
}

void printGoodbye() {
    std::cout << "Goodbye, World!" << std::endl;
}

printHelloprintGoodbye是两个独立的函数,分别表达了“打印问候语”和“打印告别语”的想法,彼此之间没有关联。

4. 保持简单(但不会令复杂的事无法实现)

例子:

// 简单的函数实现复杂的功能
template <typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

这个max函数非常简单,但它可以处理任何支持>操作符的类型,包括复杂的数据类型。

5. (如果适用的话)尽量使用静态类型检查

例子:

// 使用静态类型检查来避免运行时错误
int multiply(int a, int b) {
    return a * b;
}

在这个例子中,multiply函数的参数和返回值都是int类型,编译器会在编译时检查类型是否正确,避免了运行时类型错误。

6. 保持信息局部性(例如,避免全局变量、尽量减少指针的使用)

例子:

// 使用局部变量而不是全局变量
void processData() {
    int localData = 42; // 局部变量
    // 处理数据
}

在这个例子中,localData是一个局部变量,它的作用域仅限于processData函数,避免了全局变量带来的复杂性。

7. 不要过分抽象化(即,没有明显的需求时不要使用泛型、引入类层次或是进行参数化)

例子:

// 简单的函数,不需要泛型或类层次
int square(int x) {
    return x * x;
}

在这个例子中,square函数非常简单,直接计算一个整数的平方。没有引入不必要的泛型或类层次结构。

7 利用C++11的新特性所提供的机会来更新你的设计和编程技术,使之更加现代化:

以下是利用C++11新特性更新设计和编程技术的具体例子,展示了如何使代码更加现代化:


1. 使用构造函数建立不变式

例子:

class Circle {
public:
    Circle(double r) : radius(r) {
        if (radius <= 0) {
            throw std::invalid_argument("Radius must be positive");
        }
    }
    double area() const {
        return 3.14159 * radius * radius;
    }
private:
    double radius;
};
  • 解释:构造函数确保radius必须为正数,建立了类的不变式。如果违反不变式,抛出异常。

2. 配合使用构造/析构函数来简化资源管理

例子:

class FileHandler {
public:
    FileHandler(const std::string& filename) : file(filename) {
        if (!file.is_open()) {
            throw std::runtime_error("Failed to open file");
        }
    }
    ~FileHandler() {
        file.close();
    }
    void write(const std::string& content) {
        file << content;
    }
private:
    std::ofstream file;
};
  • 解释:构造函数打开文件,析构函数关闭文件,确保资源在对象生命周期结束时自动释放。

3. 避免"裸的"new 和 delete

例子:

void processData() {
    std::vector<int> data(100); // 使用标准库容器,避免手动管理内存
    // 使用 data
}
  • 解释:使用std::vector代替手动分配和释放内存,避免内存泄漏和错误。

4. 使用容器和算法而不是内置数组和专用代码

例子:

#include 
#include 

void processNumbers() {
    std::vector<int> numbers = {3, 1, 4, 1, 5, 9};
    std::sort(numbers.begin(), numbers.end()); // 使用标准库算法
    for (int num : numbers) {
        std::cout << num << " ";
    }
}
  • 解释:使用std::vectorstd::sort代替手动实现数组和排序算法。

5. 优先使用标准库特性而非自己开发的代码

例子:

#include 

void useSmartPointers() {
    auto ptr = std::make_unique<int>(42); // 使用标准库的智能指针
    std::cout << *ptr << std::endl;
}
  • 解释:使用std::make_unique代替手动管理内存,减少错误。

6. 使用异常而非错误代码来报告不能局部处理的错误

例子:

double divide(double a, double b) {
    if (b == 0) {
        throw std::invalid_argument("Division by zero");
    }
    return a / b;
}
  • 解释:使用异常报告错误,避免返回错误代码,使代码更清晰。

7. 使用移动语义来避免拷贝大对象

例子:

#include 

std::vector<int> createLargeVector() {
    std::vector<int> vec(1000000, 42);
    return vec; // 使用移动语义避免拷贝
}

void useLargeVector() {
    auto vec = createLargeVector(); // 移动而非拷贝
}
  • 解释:通过返回值优化(RVO)或移动语义,避免拷贝大对象。

8. 使用 unique_ptr 来引用多态类型的对象

例子:

#include 

class Base {
public:
    virtual void print() const = 0;
    virtual ~Base() = default;
};

class Derived : public Base {
public:
    void print() const override {
        std::cout << "Derived" << std::endl;
    }
};

void useUniquePtr() {
    std::unique_ptr<Base> ptr = std::make_unique<Derived>();
    ptr->print();
}
  • 解释:使用std::unique_ptr管理多态对象,确保资源自动释放。

9. 使用 shared_ptr 来引用共享对象

例子:

#include 

class Resource {
public:
    void use() {
        std::cout << "Using resource" << std::endl;
    }
};

void useSharedPtr() {
    auto res1 = std::make_shared<Resource>();
    auto res2 = res1; // 共享所有权
    res1->use();
    res2->use();
}
  • 解释:使用std::shared_ptr管理共享资源,确保资源在所有所有者释放后析构。

10. 使用模板来保持静态类型安全

例子:

template <typename T>
T add(T a, T b) {
    return a + b;
}

void useTemplate() {
    auto result = add(3, 4); // 类型安全,无需类型转换
    std::cout << result << std::endl;
}
  • 解释:使用模板实现泛型编程,保持静态类型安全,避免不必要的类层次结构。

综合示例

以下是一个综合示例,展示如何结合这些特性:

#include 
#include 
#include 
#include 
#include 

class Shape {
public:
    virtual double area() const = 0;
    virtual ~Shape() = default;
};

class Circle : public Shape {
public:
    Circle(double r) : radius(r) {
        if (radius <= 0) {
            throw std::invalid_argument("Radius must be positive");
        }
    }
    double area() const override {
        return 3.14159 * radius * radius;
    }
private:
    double radius;
};

void processShapes() {
    std::vector<std::unique_ptr<Shape>> shapes;
    shapes.push_back(std::make_unique<Circle>(5.0));

    for (const auto& shape : shapes) {
        std::cout << "Area: " << shape->area() << std::endl;
    }
}

int main() {
    try {
        processShapes();
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }
    return 0;
}
  • 解释
    1. 使用构造函数建立不变式(Circle的半径必须为正)。
    2. 使用智能指针(std::unique_ptr)管理多态对象。
    3. 使用容器(std::vector)和算法。
    4. 使用异常报告错误。
    5. 使用模板和标准库特性。

通过这些例子,可以看到C++11的新特性如何使代码更现代化、更安全、更易维护。

8 对C程序员的建议:

以下是针对C程序员的建议的具体例子,展示了如何将C++的特性与C语言的习惯区分开来,并充分利用C++的优势:


1. 不要将 C++看作增加了一些特性的 C

C风格代码:

// C风格:使用全局变量和函数
int global_value = 10;

void increment() {
    global_value++;
}

C++风格代码:

// C++风格:使用类和封装
class Counter {
public:
    Counter(int initial_value) : value(initial_value) {}
    void increment() { value++; }
    int getValue() const { return value; }
private:
    int value;
};
  • 解释:C++鼓励使用类和封装来组织代码,而不是依赖全局变量和函数。

2. 不要用 C++来写 C 程序

C风格代码:

// C风格:手动管理字符串
char* concatenate(const char* str1, const char* str2) {
    char* result = malloc(strlen(str1) + strlen(str2) + 1);
    strcpy(result, str1);
    strcat(result, str2);
    return result;
}

C++风格代码:

// C++风格:使用标准库
std::string concatenate(const std::string& str1, const std::string& str2) {
    return str1 + str2;
}
  • 解释:C++标准库提供了更安全和易用的工具(如std::string),避免了手动管理内存。

3. 将 C++标准库作为学习新技术和新程序设计风格的老师

C风格代码:

// C风格:使用strcpy和strcmp
char str1[100];
strcpy(str1, "Hello");
if (strcmp(str1, "Hello") == 0) {
    printf("Strings are equal\n");
}

C++风格代码:

// C++风格:使用std::string
std::string str1 = "Hello";
if (str1 == "Hello") {
    std::cout << "Strings are equal" << std::endl;
}
  • 解释:C++标准库提供了更直观的操作符(如===),使代码更易读。

4. C++几乎从来不需要宏替换

C风格代码:

// C风格:使用宏
#define PI 3.14159
#define MAX(a, b) ((a) > (b) ? (a) : (b))

C++风格代码:

// C++风格:使用constexpr和模板
constexpr double PI = 3.14159;

template <typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}
  • 解释:C++提供了constexprenumtemplate等特性,避免了宏的使用。

5. 在真正需要一个变量时再声明它

C风格代码:

// C风格:在函数开头声明变量
int i;
for (i = 0; i < 10; i++) {
    printf("%d\n", i);
}

C++风格代码:

// C++风格:在需要时声明变量
for (int i = 0; i < 10; i++) {
    std::cout << i << std::endl;
}
  • 解释:C++允许在需要时声明变量,使代码更清晰。

6. 不要使用 malloc()

C风格代码:

// C风格:使用malloc和free
int* arr = malloc(10 * sizeof(int));
free(arr);

C++风格代码:

// C++风格:使用std::vector
std::vector<int> arr(10);
  • 解释:C++标准库提供了std::vector等容器,避免了手动管理内存。

7. 避免使用 void*、联合以及类型转换

C风格代码:

// C风格:使用void*和类型转换
void* ptr = malloc(sizeof(int));
*(int*)ptr = 42;
free(ptr);

C++风格代码:

// C++风格:使用类型安全的特性
std::unique_ptr<int> ptr = std::make_unique<int>(42);
  • 解释:C++提供了类型安全的工具(如智能指针),避免了类型转换。

8. 尽量减少数组和 C-风格字符串的使用

C风格代码:

// C风格:使用C-风格字符串
char name[100];
strcpy(name, "John");

C++风格代码:

// C++风格:使用std::string
std::string name = "John";
  • 解释:C++标准库提供了std::string,避免了手动管理字符串。

9. 避免对指针进行算术运算

C风格代码:

// C风格:指针算术运算
int arr[10];
int* p = arr;
for (int i = 0; i < 10; i++) {
    *p++ = i;
}

C++风格代码:

// C++风格:使用容器和迭代器
std::vector<int> arr(10);
for (auto it = arr.begin(); it != arr.end(); ++it) {
    *it = std::distance(arr.begin(), it);
}
  • 解释:C++标准库提供了迭代器,避免了手动指针操作。

10. 不要认为 C 风格代码更高效

C风格代码:

// C风格:手动实现排序
void sort(int* arr, int n) {
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            if (arr[i] > arr[j]) {
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
    }
}

C++风格代码:

// C++风格:使用标准库算法
std::vector<int> arr = {5, 3, 1, 4, 2};
std::sort(arr.begin(), arr.end());
  • 解释:C++标准库算法通常比手动实现的代码更高效且更安全。

11. C++函数与C程序连接

C++代码:

// C++函数声明为C连接方式
extern "C" {
    void cpp_function() {
        std::cout << "This is a C++ function with C linkage" << std::endl;
    }
}

C代码:

// C代码调用C++函数
void cpp_function();

int main() {
    cpp_function();
    return 0;
}
  • 解释:通过extern "C"声明,C++函数可以与C程序连接。

通过这些例子,C程序员可以逐步适应C++的设计理念和编程风格,充分利用C++的优势,编写出更现代化、更安全的代码。

9 对Java程序员的建议

C++和 Java 具有相似的语法,但其实是相当不同的语言。1 它们的目标和应用领域有巨大差异。Java 并不是 C++的直接继任者,因为从一般意义上讲,继任者应该能做和前任相同的事,而且做得更好也更多。为了更好地使用 C++,你应该采用适合 C++的编程和设计技术,而不是试图用 C++语言来编写 Java 程序。并不是记住你用 new 创建的对象都要 delete 就可以了,而是要了解你已不能再依赖垃圾收集器。
以下是针对C++和Java差异的具体例子,展示了如何避免用C++模仿Java风格,并充分利用C++的特性:


1. 不要简单地用 C++模仿 Java 风格

Java风格代码:

// Java风格:依赖垃圾回收
class MyClass {
    private int value;
    public MyClass(int value) {
        this.value = value;
    }
}

C++风格代码:

// C++风格:使用RAII管理资源
class MyClass {
public:
    MyClass(int value) : value(value) {}
private:
    int value;
};
  • 解释:C++不依赖垃圾回收,而是通过RAII(资源获取即初始化)管理资源。

2. 使用 C++的抽象机制

Java风格代码:

// Java风格:使用Object基类
class MyClass extends Object {
    // ...
}

C++风格代码:

// C++风格:不需要基类
class MyClass {
    // ...
};
  • 解释:C++不需要为所有类创建一个基类(如Java的Object),而是根据需求设计类层次。

3. 将 C++标准库作为学习新技术和新程序设计风格的老师

Java风格代码:

// Java风格:使用ArrayList
import java.util.ArrayList;

ArrayList<String> list = new ArrayList<>();
list.add("Hello");

C++风格代码:

// C++风格:使用std::vector
#include 
#include 

std::vector<std::string> list;
list.push_back("Hello");
  • 解释:C++标准库提供了丰富的容器和算法,避免了手动实现。

4. 不要马上为所有类创造一个唯一的基类

Java风格代码:

// Java风格:所有类继承Object
class MyClass extends Object {
    // ...
}

C++风格代码:

// C++风格:不需要基类
class MyClass {
    // ...
};
  • 解释:C++中,类层次结构应根据需求设计,而不是强制继承某个基类。

5. 尽量不使用引用和指针变量

Java风格代码:

// Java风格:使用引用
MyClass obj = new MyClass();

C++风格代码:

// C++风格:使用局部变量
MyClass obj;
  • 解释:C++中,尽量使用局部变量和成员变量,而不是指针或引用。

6. 记住:"一个变量隐含地是一个引用"不再成立了

Java风格代码:

// Java风格:变量是引用
MyClass obj1 = new MyClass();
MyClass obj2 = obj1; // obj2和obj1引用同一个对象

C++风格代码:

// C++风格:变量是值
MyClass obj1;
MyClass obj2 = obj1; // obj2是obj1的副本
  • 解释:C++中,变量默认是值类型,而不是引用类型。

7. 将指针看作 Java 的引用在 C++中的等价物

Java风格代码:

// Java风格:引用可以重新赋值
MyClass obj1 = new MyClass();
MyClass obj2 = new MyClass();
obj1 = obj2; // obj1现在引用obj2的对象

C++风格代码:

// C++风格:指针可以重新赋值
MyClass* obj1 = new MyClass();
MyClass* obj2 = new MyClass();
obj1 = obj2; // obj1现在指向obj2的对象
  • 解释:C++中,指针类似于Java的引用,但更灵活。

8. 函数不再默认是 virtual 的了

Java风格代码:

// Java风格:函数默认是virtual
class Base {
    public void foo() {
        System.out.println("Base");
    }
}

C++风格代码:

// C++风格:函数默认不是virtual
class Base {
public:
    void foo() {
        std::cout << "Base" << std::endl;
    }
};
  • 解释:C++中,函数默认不是virtual,需要显式声明。

9. 将抽象类作为类层次的接口

Java风格代码:

// Java风格:使用接口
interface MyInterface {
    void foo();
}

C++风格代码:

// C++风格:使用抽象类
class MyInterface {
public:
    virtual void foo() = 0;
    virtual ~MyInterface() = default;
};
  • 解释:C++中,抽象类可以作为接口使用,避免“脆弱的基类”问题。

10. 使用限定作用域的资源管理(RAII)

Java风格代码:

// Java风格:使用try-finally
FileInputStream file = new FileInputStream("file.txt");
try {
    // 使用文件
} finally {
    file.close();
}

C++风格代码:

// C++风格:使用RAII
#include 

void processFile() {
    std::ifstream file("file.txt");
    // 使用文件
    // 文件在作用域结束时自动关闭
}
  • 解释:C++中,RAII确保资源在作用域结束时自动释放。

11. 使用构造函数建立类的不变式

C++风格代码:

class MyClass {
public:
    MyClass(int value) : value(value) {
        if (value < 0) {
            throw std::invalid_argument("Value must be non-negative");
        }
    }
private:
    int value;
};
  • 解释:构造函数确保value必须为非负数,建立类的不变式。

12. 使用析构函数进行清理

C++风格代码:

class FileHandler {
public:
    FileHandler(const std::string& filename) : file(filename) {}
    ~FileHandler() {
        file.close();
    }
private:
    std::ofstream file;
};
  • 解释:析构函数确保文件在对象销毁时关闭。

13. 避免使用"裸的"new 和 delete

C++风格代码:

#include 

void processData() {
    auto ptr = std::make_unique<int>(42); // 使用智能指针
}
  • 解释:使用智能指针和容器(如std::vector)避免手动管理内存。

14. 使用独立函数和名字空间

C++风格代码:

namespace MyNamespace {
    void foo() {
        std::cout << "Hello" << std::endl;
    }
}

int main() {
    MyNamespace::foo();
    return 0;
}
  • 解释:使用名字空间限制函数作用域,减少耦合。

15. 不要使用异常规范

C++风格代码:

void foo() noexcept {
    // 不会抛出异常
}
  • 解释:C++中,避免使用异常规范(除noexcept外)。

16. C++嵌套类对外围类的对象没有访问权限

C++风格代码:

class Outer {
public:
    class Inner {
    public:
        void foo() {
            // 无法访问Outer的成员
        }
    };
};
  • 解释:C++嵌套类不能直接访问外围类的成员。

17. 依靠编译时特性

C++风格代码:

template <typename T>
void printType() {
    std::cout << typeid(T).name() << std::endl;
}

int main() {
    printType<int>(); // 输出int类型信息
    return 0;
}
  • 解释:C++更多依赖编译时特性(如模板),而不是运行时反射。

大多数建议对 C#程序员也适用。

10 C++的历史:

C++98在语言特性方面,特别是规范细节方面,要远胜过 1989 年的版本。但是,并非所有的变化都是改进。现在回想起来,除了一些不可避免的小错误,有两个主要的新特性当时也不应该加入:

  • 异常说明可以指定一个函数运行时可以抛出哪些异常。这个特性是在 Sun 微系统公司的人的积极推动下加入的。异常说明已经被证明对于提高可读性、可靠性和性能是有害无益的,在 2011 标准中已被弃用(计划将来删除)。2011 标准引入了 noexcept,它可以解决那些本来希望异常说明能解决的问题,但更简单。
  • 显然,将模板的编译和使用分离是理想的方式[Stroustrup,1994],但由于模板实际使用带来的一些限制,如何实现这一方式就一点儿也不显然了。委员会经过长时间的争论达成了妥协——将模板 export 作为 1998 标准的一部分。对此问题这并不是一个优雅的解决方案,只有一家厂商实现了 export(爱迪生设计集团),2011 标准中已将此特性删除。我们仍在寻找更好的解决方案。我的观点是,根本问题不在于分离编译本身,而是模板的接口和实现之间的差别并不明确。因此,export 解决的是一个错误的问题。未来,通过提供模板需求的准确说明可能会对语言支持“概念”有所帮助。目前这个领域的研究和设计都很活跃[Sutton,2011][Stroustrup,2012a]。

只要是接触了 C++ 有一定时间的程序员,都会记住这么一个不成文的规定:类模板的声明与实现最好放在同一个头文件中,引用《Data Structure And Algorithm Analysis In C++》中的解释:就像类一样,类模板是可以将其实现与声明放在一起的,或者也可以将接口与实现分离。但是呢,编译器由于历史原因对于分离式编译的支持非常弱,并且因平台的不同支持力度有所不同。

11 建议:

对于初学者,下面列出了一些来自 C++的设计、学习和历史这几节的建议:
[1]用代码直接表达想法(概念),例如,表达为一个函数、一个类或是一个枚举;
[2]编写代码应以优雅且高效为目标。
[3]不要过度抽象。
[4]设计应关注提供优雅且高效的抽象,可能的情况下以库的形式呈现。
[5]用代码直接表达想法之间的关联,例如,通过参数化或类层次。
[6]无关的想法应用独立的代码表达,例如,避免类之间的相互依赖。
[7]C++并不只是面向对象的。
[8]C++并不只是用于泛型编程。
[9]优选可以进行静态检查的方案。
[10]令资源是显式的(将它们表示为类对象)。
[11]简单的想法应简单表达。
[12]使用库,特别是标准库,不要试图从头开始构建所有东西。
[13]使用类型丰富的程序设计风格。
[14]低层代码不一定高效;不要因为担心性能问题而回避类、模板和标准库组件。
[15]如果数据具有不变量,封装它。
[16]C++并非 C 的简单扩展。

你可能感兴趣的:(C++笔记,c++,笔记,经验分享)