C++里最关键的概念是类。一个类就是一个用户定义类型。类提供了对数据的隐藏,数据的初始化保证,用户定义类型的隐式类型转换,动态类型识别,用户控制的存储管理,以及重载运算符的机制等。在类型检查和表述模块性方面,C++提供了比C好得多的功能。它还包含了许多并不直接与类相关的改进,包括符号常量、函数的在线替换、默认函数参数、重载函数名、自由存储管理运算符,以及引用类型等。C++保持了C高效处理硬件基本对象(位、字节、字、地址等)的能力。这就使用户定义类型能够在相当高的效率水平上实现。
程序设计风格;类型检查;C 兼容性;语言、库和系统
将内置操作和内置类型直接映射到硬件,从而提供高效的内存利用和高效的底层操作;
灵活且低开销的抽象机制,使得用户自定义类型无论是符号表达、使用范围还是性能都能与内置类型相当。
1.不给比 C++更底层的语言留任何余地(在极少的情况下汇编语言是例外)。因为,如果你能用一种更底层的语言编写出更高效的代码,那意味着这种语言很可能比 C++更适合系统程序设计。
2.你不使用它,就不要为它付出代价。如果程序员能够手工编写出很不错的代码,来模拟一个语言特性或是一个基础的抽象机制,甚至性能更好一些,那么一些人就真的会去编写这种代码,而很多人就会效仿。因此,与等价的替代方法相比,我们设计的语言特性或是基础的抽象机制必须不浪费哪怕一个字节或是一个处理器时钟周期。这就是众所周知的零开销原则(zero-overhead principle)。
1.用代码直接表达想法;
2.无关的想法应独立表达;
3.用代码直接描述想法之间的关联;
4.可以自由的组合用代码表达的想法,但仅在这种组合有意义时;
5.简单的想法应简单表达。
以下是针对C++支持的四种程序设计风格的具体例子,展示了如何组合使用这些风格来编写高效、灵活且易于维护的程序:
例子:
#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
定义数据结构,通过函数操作数据,体现了过程式程序设计的风格。例子:
#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
方法),隐藏实现细节,体现了数据抽象的风格。例子:
#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
和派生类Circle
、Square
实现多态,体现了面向对象程序设计的风格。例子:
#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
,支持多种类型,体现了泛型程序设计的风格。例子:
#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
)、泛型编程(模板)和面向对象(类方法)。例子:
class BankAccount {
public:
BankAccount(double balance) : balance(balance) {}
void deposit(double amount) {
balance += amount;
}
double getBalance() const {
return balance;
}
private:
double balance;
};
public
和private
分离接口和实现,支持数据抽象和面向对象程序设计。例子:
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;
};
例子:
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;
}
例子:
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;
}
};
例子:
inline int square(int x) {
return x * x;
}
int main() {
std::cout << square(5) << std::endl;
return 0;
}
C++中的基本对象具有唯一的身份,即它们位于内存中的定位置,而且可以通过比较地址与(可能)具有相同值的其他对象区分开来。表示这种对象的表达式被称为左值(Ivalue)。但早在 C++的祖先[Barron,1963]月所在的年代,就已经有了没有身份的对象(对于这类对象,不存在一个安全存储的地址可供随后使用)。在 C++11 中,这一右值(rvalue)的概念发展为一个新的概念–不能以低开销进行移动的值。以这种对象为基础的技术很像函数式程序设计中所用的技术(在函数式程序设计中,有身份的对象的概念是令人反感的)。这一新概念对泛型程序设计技术和语言特性(如 lambda 表达式)是很好的补充。它还很好地解决了与"简单抽象数据类型"相关的一些问题,例如,如何从一个操作(如矩阵+)优雅而高效地返回一个大矩阵。
下面是一些例子,展示如何用C++编写出好的程序:
例子:
// 直接表达“计算两个数的和”的想法
int add(int a, int b) {
return a + b;
}
在这个例子中,代码直接表达了“计算两个数的和”的想法,函数名add
和操作a + b
都非常直观。
例子:
// 使用类层次结构表达“形状”和“圆形”之间的关联
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
类,表达了“圆形是一种形状”的层次关系。
例子:
// 独立的函数表达不同的想法
void printHello() {
std::cout << "Hello, World!" << std::endl;
}
void printGoodbye() {
std::cout << "Goodbye, World!" << std::endl;
}
printHello
和printGoodbye
是两个独立的函数,分别表达了“打印问候语”和“打印告别语”的想法,彼此之间没有关联。
例子:
// 简单的函数实现复杂的功能
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
这个max
函数非常简单,但它可以处理任何支持>
操作符的类型,包括复杂的数据类型。
例子:
// 使用静态类型检查来避免运行时错误
int multiply(int a, int b) {
return a * b;
}
在这个例子中,multiply
函数的参数和返回值都是int
类型,编译器会在编译时检查类型是否正确,避免了运行时类型错误。
例子:
// 使用局部变量而不是全局变量
void processData() {
int localData = 42; // 局部变量
// 处理数据
}
在这个例子中,localData
是一个局部变量,它的作用域仅限于processData
函数,避免了全局变量带来的复杂性。
例子:
// 简单的函数,不需要泛型或类层次
int square(int x) {
return x * x;
}
在这个例子中,square
函数非常简单,直接计算一个整数的平方。没有引入不必要的泛型或类层次结构。
以下是利用C++11新特性更新设计和编程技术的具体例子,展示了如何使代码更加现代化:
例子:
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
必须为正数,建立了类的不变式。如果违反不变式,抛出异常。例子:
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;
};
例子:
void processData() {
std::vector<int> data(100); // 使用标准库容器,避免手动管理内存
// 使用 data
}
std::vector
代替手动分配和释放内存,避免内存泄漏和错误。例子:
#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::vector
和std::sort
代替手动实现数组和排序算法。例子:
#include
void useSmartPointers() {
auto ptr = std::make_unique<int>(42); // 使用标准库的智能指针
std::cout << *ptr << std::endl;
}
std::make_unique
代替手动管理内存,减少错误。例子:
double divide(double a, double b) {
if (b == 0) {
throw std::invalid_argument("Division by zero");
}
return a / b;
}
例子:
#include
std::vector<int> createLargeVector() {
std::vector<int> vec(1000000, 42);
return vec; // 使用移动语义避免拷贝
}
void useLargeVector() {
auto vec = createLargeVector(); // 移动而非拷贝
}
例子:
#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
管理多态对象,确保资源自动释放。例子:
#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
管理共享资源,确保资源在所有所有者释放后析构。例子:
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;
}
Circle
的半径必须为正)。std::unique_ptr
)管理多态对象。std::vector
)和算法。通过这些例子,可以看到C++11的新特性如何使代码更现代化、更安全、更易维护。
以下是针对C程序员的建议的具体例子,展示了如何将C++的特性与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风格代码:
// 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;
}
std::string
),避免了手动管理内存。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风格代码:
// 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;
}
constexpr
、enum
、template
等特性,避免了宏的使用。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风格代码:
// C风格:使用malloc和free
int* arr = malloc(10 * sizeof(int));
free(arr);
C++风格代码:
// C++风格:使用std::vector
std::vector<int> arr(10);
std::vector
等容器,避免了手动管理内存。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风格代码:
// C风格:使用C-风格字符串
char name[100];
strcpy(name, "John");
C++风格代码:
// C++风格:使用std::string
std::string name = "John";
std::string
,避免了手动管理字符串。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风格代码:
// 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++代码:
// 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++的优势,编写出更现代化、更安全的代码。
C++和 Java 具有相似的语法,但其实是相当不同的语言。1 它们的目标和应用领域有巨大差异。Java 并不是 C++的直接继任者,因为从一般意义上讲,继任者应该能做和前任相同的事,而且做得更好也更多。为了更好地使用 C++,你应该采用适合 C++的编程和设计技术,而不是试图用 C++语言来编写 Java 程序。并不是记住你用 new 创建的对象都要 delete 就可以了,而是要了解你已不能再依赖垃圾收集器。
以下是针对C++和Java差异的具体例子,展示了如何避免用C++模仿Java风格,并充分利用C++的特性:
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;
};
Java风格代码:
// Java风格:使用Object基类
class MyClass extends Object {
// ...
}
C++风格代码:
// C++风格:不需要基类
class MyClass {
// ...
};
Object
),而是根据需求设计类层次。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");
Java风格代码:
// Java风格:所有类继承Object
class MyClass extends Object {
// ...
}
C++风格代码:
// C++风格:不需要基类
class MyClass {
// ...
};
Java风格代码:
// Java风格:使用引用
MyClass obj = new MyClass();
C++风格代码:
// C++风格:使用局部变量
MyClass obj;
Java风格代码:
// Java风格:变量是引用
MyClass obj1 = new MyClass();
MyClass obj2 = obj1; // obj2和obj1引用同一个对象
C++风格代码:
// C++风格:变量是值
MyClass obj1;
MyClass obj2 = obj1; // obj2是obj1的副本
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的对象
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;
}
};
virtual
,需要显式声明。Java风格代码:
// Java风格:使用接口
interface MyInterface {
void foo();
}
C++风格代码:
// C++风格:使用抽象类
class MyInterface {
public:
virtual void foo() = 0;
virtual ~MyInterface() = default;
};
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++风格代码:
class MyClass {
public:
MyClass(int value) : value(value) {
if (value < 0) {
throw std::invalid_argument("Value must be non-negative");
}
}
private:
int value;
};
value
必须为非负数,建立类的不变式。C++风格代码:
class FileHandler {
public:
FileHandler(const std::string& filename) : file(filename) {}
~FileHandler() {
file.close();
}
private:
std::ofstream file;
};
C++风格代码:
#include
void processData() {
auto ptr = std::make_unique<int>(42); // 使用智能指针
}
std::vector
)避免手动管理内存。C++风格代码:
namespace MyNamespace {
void foo() {
std::cout << "Hello" << std::endl;
}
}
int main() {
MyNamespace::foo();
return 0;
}
C++风格代码:
void foo() noexcept {
// 不会抛出异常
}
noexcept
外)。C++风格代码:
class Outer {
public:
class Inner {
public:
void foo() {
// 无法访问Outer的成员
}
};
};
C++风格代码:
template <typename T>
void printType() {
std::cout << typeid(T).name() << std::endl;
}
int main() {
printType<int>(); // 输出int类型信息
return 0;
}
大多数建议对 C#程序员也适用。
C++98在语言特性方面,特别是规范细节方面,要远胜过 1989 年的版本。但是,并非所有的变化都是改进。现在回想起来,除了一些不可避免的小错误,有两个主要的新特性当时也不应该加入:
只要是接触了 C++ 有一定时间的程序员,都会记住这么一个不成文的规定:类模板的声明与实现最好放在同一个头文件中,引用《Data Structure And Algorithm Analysis In C++》中的解释:就像类一样,类模板是可以将其实现与声明放在一起的,或者也可以将接口与实现分离。但是呢,编译器由于历史原因对于分离式编译的支持非常弱,并且因平台的不同支持力度有所不同。
对于初学者,下面列出了一些来自 C++的设计、学习和历史这几节的建议:
[1]用代码直接表达想法(概念),例如,表达为一个函数、一个类或是一个枚举;
[2]编写代码应以优雅且高效为目标。
[3]不要过度抽象。
[4]设计应关注提供优雅且高效的抽象,可能的情况下以库的形式呈现。
[5]用代码直接表达想法之间的关联,例如,通过参数化或类层次。
[6]无关的想法应用独立的代码表达,例如,避免类之间的相互依赖。
[7]C++并不只是面向对象的。
[8]C++并不只是用于泛型编程。
[9]优选可以进行静态检查的方案。
[10]令资源是显式的(将它们表示为类对象)。
[11]简单的想法应简单表达。
[12]使用库,特别是标准库,不要试图从头开始构建所有东西。
[13]使用类型丰富的程序设计风格。
[14]低层代码不一定高效;不要因为担心性能问题而回避类、模板和标准库组件。
[15]如果数据具有不变量,封装它。
[16]C++并非 C 的简单扩展。