C++基础-自学笔记(27-29)

该博客围绕C++展开,介绍了使用流进行输入输出,如std::cout、std::cin和std::fstream的操作;阐述了异常处理,包括try和catch的使用、std::exception类等;还提及多线程编程,如线程概念、线程同步及多线程问题,最后给出编写优质C++代码的建议和学习资源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第 27 章 使用流进行输入和输出

27.1 流的概述

假设您要开发一个程序,它从磁盘读取数据,将数据显示到屏幕上,从键盘读取用户输入以及将数据存储到磁盘中。在这种情况下,倘若不管数据来自或前往什么设备或位置,都能以相同的方式处理读写操作,那该有多好!这正是 C++流提供的功能。

C++流是读写(输入和输出)逻辑的通用实现,让您能够用统一的模式读写数据。不管是磁盘或键盘读取数据,还是将输入写入显示器或磁盘,这些模式都相同。您只需使用合适的流类,类的实现
将负责处理与设备和操作系统相关的细节。

std:cout 是 ostream 类的一个对象,用于输出到控制台。要使用 std::cout,需要包含提供它的头文件,这个头文件还提供了 std::cin,让您能够从流中读取数据。

流让您能够以一致的方式访问不同的设备时,是什么意思呢?如果要将 Hello World 写入文本文件,可将同样的语法用于文件流对象 fsHello:fsHello << "Hello World!" << endl;

27.2 重要的 C++流类和流对象

std 命名空间中常用的 C++流类

类/对象用途
cout标准输出流,通常被重定向到控制台
cin标准输入流,通常用于将数据读入变量
cerr用于显示错误信息的标准输出流
fstream用于操作文件的输入和输出流,继承了 ofstream 和 ifstream
ofstream用于操作文件的输出流类,即用于创建文件
ifstream用于操作文件的输入流类,即用于读取文件
stringstream用于操作字符串的输入和输出流类,继承了 istringstream 和 ostringstream,通常用于在字符串和其他类型之间进行转换

std 命名空间中常用于流的控制符

27.3 std::cout

27.3.1 使用 std::cout 修改数字的显示格式

可以让 cout 以十六进制或八进制方式显示整数。

 0: #include <iostream>
 1: #include <iomanip>
 2: using namespace std;
 3:
 4: int main()
 5: {
 6: cout << "Enter an integer: ";
 7: int input = 0;
 8: cin >> input;
 9:
10: cout << "Integer in octal: " << oct << input << endl;
11: cout << "Integer in hexadecimal: " << hex << input << endl;
12:
13: cout << "Integer in hex using base notation: ";
14: cout<<setiosflags(ios_base::hex|ios_base::showbase|ios_base::uppercase);
15: cout << input << endl;
16:
17: cout << "Integer after resetting I/O flags: ";
18: cout<<resetiosflags(ios_base::hex|ios_base::showbase|ios_base::uppercase);
19: cout << input << endl;
20:
21: return 0;
22: }
输出:
Enter an integer: 253
Integer in octal: 375
Integer in hexadecimal: fd
Integer in hex using base notation: 0XFD
Integer after resetting I/O flags: 253

这个代码示例使用了表 27.2 所示的控制符,以修改 cout 显示用户输入的整数 input 的方式。注意到第 10 和 11 行使用了控制符 oct 和 hex。第 14 行使用了 setiosflags( )让 cout 以十六进制方式(并使用大写字母)显示该数字,其结果是 cout 将 253 显示为OXFD。第 18 行使用了 resetiosflags( ),其效果是再次使用 cout 显示该整数时,将显示为十进制。要将显示整数时使用的基数改为十进制,也可使用下面这种方式:

cout << dec << input << endl; // displays in decimal

对于诸如 Pi 等数字,可指定 cout 显示它们时使用的精度(小数点后面的位数),还可指定以定点表示法或科学表示法显示它们。

 0: #include <iostream>
 1: #include <iomanip>
 2: using namespace std;
 3:
 4: int main()
 5: {
 6: const double Pi = (double)22.0 / 7;
 7: cout << "Pi = " << Pi << endl;
 8:
 9: cout << endl << "Setting precision to 7: " << endl;
10: cout << setprecision(7);
11: cout << "Pi = " << Pi << endl;
12: cout << fixed << "Fixed Pi = " << Pi << endl;
13: cout << scientific << "Scientific Pi = " << Pi << endl;
14:
15: cout << endl << "Setting precision to 10: " << endl;
16: cout << setprecision(10);
17: cout << "Pi = " << Pi << endl;
18: cout << fixed << "Fixed Pi = " << Pi << endl;
19: cout << scientific << "Scientific Pi = " << Pi << endl;
20:
21: cout << endl << "Enter a radius: ";
22: double radius = 0.0;
23: cin >> radius;
24: cout << "Area of circle: " << 2*Pi*radius*radius << endl;
25:
26: return 0;
27: }
输出:
Pi = 3.14286
Setting precision to 7:
Pi = 3.142857
Fixed Pi = 3.1428571
Scientific Pi = 3.1428571e+000
Setting precision to 10:
Pi = 3.1428571429e+000
Fixed Pi = 3.1428571429
Scientific Pi = 3.1428571429e+000
Enter a radius: 9.99
Area of circle: 6.2731491429e+002

输出表明,第 7 行和第 10 行分别将精度设置为 7 和 10 后,显示的 Pi 值不同。另外,控制符 scientific导致计算得到的圆面积被显示为 6.2731491429e+002。

27.3.2 使用 std::cout 对齐文本和设置字段宽度

可使用 setw( )控制符来设置字段宽度,插入到流中的内容将在指定宽度内右对齐。在这种情况下,还可使用 setfill( )指定使用什么字符来填充空白区域。

 0: #include <iostream>
 1: #include <iomanip>
 2: using namespace std;
 3:
 4: int main()
 5: {
 6: cout << "Hey - default!" << endl;
 7:
 8: cout << setw(35); // set field width to 25 columns
 9: cout << "Hey - right aligned!" << endl;
10:
11: cout << setw(35) << setfill('*');
12: cout << "Hey - right aligned!" << endl;
13:
14: cout << "Hey - back to default!" << endl;
15:
16: return 0;
17: }
输出:
Hey - default!
 Hey - right aligned!
***************Hey - right aligned!
Hey - back to default! 

第 8 行使用了 setw(35),而第 11 行使用了 setw(35)和 setfill(‘*’),输出说明了这样做的效果。从输出可知,第 11 行导致使用 setfill( )指定的星号来填充文本前的空白区域。

27.4 std::cin

27.4.1 使用 std::cin 将输入读取到基本类型变量中

使用 cin 可将标准输入读取到 int、double 和 char 变量中。

int inputNum = 0;
cin >> inputNum;

27.4.2 使用 std::cin:get 将输入读取到 char*缓冲区中

也可将输入直接写入 char 数组(C 风格字符串):

cout << "Enter a line: " << endl;
char charBuf [10] = {0}; // can contain max 10 chars
cin >> charBuf; // Danger: user may enter more than 10 chars 

写入 C 风格字符串缓冲区时,务必不要超越缓冲区的边界,以免导致程序崩溃或带来安全隐患,这至关重要。因此,将输入读取到 char 数组(C 风格字符串)时,下面是一种更好的方法:

cout << "Enter a line: " << endl;
char charBuf[10] = {0};
cin.get(charBuf, 9); // stop inserting at the 9th character 

案例

#include <iostream>
using namespace std;

int main(){
    cout << "enter a line:" << endl;
    char str[10];
    cin.get(str,9); //the 10th is '\0'
    cout << str << endl;
    return 0;
}

27.4.3 使用 std::cin 将输入读取到 std::string 中

cin 用途广泛,甚至可使用它将用户输入的字符串直接读取到 std::string 中:

0: #include<iostream>
 1: #include<string>
 2: using namespace std;
 3:
 4: int main()
 5: {
 6: 	cout << "Enter your name: ";
 7: 	string name;
 8: 	cin >> name;
 9: 	cout << "Hi " << name << endl;
10:
11: return 0;
12: }
输出:
Enter your name: Siddhartha Rao
Hi Siddhartha

由上述输出结果可知,cin 遇到空白后停止插入
要读取整行输入(包括空白),需要使用 getline( )

string name;
getline(cin, name); 
 0: #include<iostream>
 1: #include<string>
 2: using namespace std;
 3:
 4: int main()
 5: {
 6: 	cout << "Enter your name: ";
 7: 	string name;
 8: 	getline(cin, name);
 9: 	cout << "Hi " << name << endl;
10:
11: 	return 0;
12: }
输出:
Enter your name: Siddhartha Rao
Hi Siddhartha Rao

27.5 使用 std::fstream 处理文件

C++提供了 std::fstream,旨在以独立于平台的方式访问文件。std::fstream 从 std::ofstream 那里继承了写入文件的功能,并从 std::ifstream 那里继承了读取文件的功能。

要使用 std::fstream 类或其基类,需要包含头文件:
#include、

27.5.1 使用 open( )和 close( )打开和关闭文件

open( )接受两个参数:第一个是要打开的文件的路径和名称(如果没有提供路径,将假定为应用程序的当前目录设置);第二个是文件的打开模式。

fstream myFile;
myFile.open("HelloFile.txt",ios_base::in|ios_base::out|ios_base::trunc);
if (myFile.is_open()) // check if open() succeeded
{
 // do reading or writing here
 myFile.close();
} 

文件的打开模式:

  1. ios_base::app:附加到现有文件末尾,而不是覆盖它。
  2. ios_base::ate:切换到文件末尾,但可在文件的任何地方写入数据。
  3. ios_base::trunc:导致现有文件被覆盖,这是默认设置。
  4. ios_base::binary:创建二进制文件(默认为文本文件)。
  5. ios_base::in:以只读方式打开文件。
  6. ios_base::out:以只写方式打开文件。

还有另一种打开文件流的方式,那就是使用构造函数:

fstream myFile("HelloFile.txt",ios_base::in|ios_base::out|ios_base::trunc);

【警告】保存到文件时,必须使用 close()关闭文件流。

27.5.2 使用 open( )创建文本文件并使用运算符<<写入文本

#include <iostream>
#include <fstream>
using namespace std;

int main(){
    ofstream ofs;
    ofs.open("test.txt", ios::out);

    if (ofs.is_open()){
        ofs << "hello fstream" << endl;
    }
    ofs.close();
    return 0;
}

27.5.3 使用 open( )和运算符>>读取文本文件

#include <iostream>
#include <fstream>
using namespace std;

int main(){
    ifstream ifs;
    ifs.open("test.txt", ios::in);

    if (ifs.is_open()){
        cout << "open file success" << endl;

        string content;
        while(ifs.good()){
            getline(ifs,content);
            cout << content << endl;
        }
    }
    ifs.close();
    return 0;
}

27.5.4 读写二进制文件

在打开文件时使用 ios_base::binary标志。通常使用 ofstream::write 和 ifstream::read 来读写二进制文件。

#include <iostream>
#include <fstream>
#include<iomanip>
using namespace std;

class Person{
public:
    string name;
    int age;
    Person(){}
    Person(string name, int age):name(name),age(age){}
};

int main(){
    Person person("shahao",23);

    ofstream ofs("binary.txt",ios::binary);
    if (ofs.is_open()){
        cout << "binary.txt open success" << endl;
        ofs.write(reinterpret_cast<const char*>(&person),sizeof(person));
        ofs.close();
        cout << "binary.txt write success" << endl;
    }

    ifstream ifs("binary.txt",ios::binary | ios::in);
    if (ifs.is_open()){
        cout << "binary.txt open success" << endl;

        Person person1;
        ifs.read((char*)&person1, sizeof(Person));
        cout << "Reading information from binary file: " << endl;
        cout << "Name = " << person1.name << endl;
        cout << "Age = " << person1.age << endl;

        ifs.close();
    }

    return 0;
}

输出结果:
binary.txt open success
binary.txt write success
binary.txt open success
Reading information from binary file: 
Name = shahao
Age = 23

27.6 使用 std::stringstream 对字符串进行转换

要使用 std::stringstream 类,需要包含头文件:
#include<sstream>

#include <iostream>
#include <sstream>
using namespace std;

int main(){
    cout << "enter a int num = ";
    int num = 0;
    cin >> num;
    cout << endl;

    //整型变字符串
    stringstream  ss;
    ss << num;
    string str;
    ss >> str;

    cout << "int num = " <<num << endl;
    cout << "string gained from int : " <<str << endl;

    cout << endl;

    //字符串变整型
    stringstream ss1;
    ss1 << str;
    int num1 = 0;
    ss1 >> num1;
    cout << "string str = " << str << endl;
    cout << "int gained from string : " << num1 << endl;
    
    return 0;
}
输出结果:
enter a int num = 400

int num = 400               
string gained from int : 400
                            
string str = 400            
int gained from string : 400

第 28 章 异常处理

28.1 使用 try 和 catch 捕获异常

28.1.1 使用 catch(…)处理所有异常

#include <iostream>
using namespace std;

int main(){
    int num = 0;
    cout << "num = ";
    cin >> num;

    try{
        int * arr = new int[num];
        delete [] arr;
    }catch(...){
        cout << "Exception occurred. Got to end, sorry!" << endl;
    }

    return 0;
}

输出结果:
num = -1
Exception occurred. Got to end, sorry!

28.3.2 捕获特定类型的异常

程序清单 28.1 所示的异常是由 C++标准库引发的。这种异常的类型是已知的,在这种情况下,更好的选择是只捕获这种类型的异常,因为您能查明导致异常的原因,执行更有针对性的清理工作,或至少是向用户显示一条准确的消息。

#include <iostream>
using namespace std;

int main(){
    int num = 0;
    cout << "num = ";
    cin >> num;

    try{
        int * arr = new int[num];
        delete [] arr;
    }catch(std::bad_alloc& exp){
        cout << "Exception encountered: " << exp.what() << endl;
        cout << "Got to end, sorry!" << endl;
    }catch (...){
        cout << "Exception encountered. Got to end, sorry!" << endl;-1
    }

    return 0;
}

输出结果:
num = -1
Exception encountered: std::bad_array_new_length
Got to end, sorry!

28.3.3 使用 throw 引发特定类型的异常

#include <iostream>
using namespace std;

double Divide(double dividend, double divisor){
    if (divisor == 0)
        throw "Dividing by 0 is a crime";
    return dividend/divisor;
}

int main(){
    double i = 10, j = 0;
    try{
        auto res = Divide(i,j);
    }catch (const char* exp){
        cout << "Exception: " << exp << endl;
        cout << "Sorry, can't continue!" << endl;
    }
    return 0;
}
输出结果:
Exception: Dividing by 0 is a crime
Sorry, can't continue!

28.4 异常处理的工作原理

在函数 Divide( )中引发了一个类型为 char的异常,并在函数 main( )中使用处理程序 catch(char)捕获它。

每当您使用 throw 引发异常时,编译器都将查找能够处理该异常的 catch(Type)。

异常处理逻辑首先检查引发异常的代码是否包含在 try 块中,如果是,则查找可处理这种异常的 catch(Type)。如果 throw语句不在 try 块内,或者没有与引发的异常兼容的 catch( ),异常处理逻辑将继续在调用函数中寻找。

因此,异常处理逻辑沿调用栈向上逐个地在调用函数中寻找,直到找到可处理异常的 catch(Type)。在退栈过程的每一步中,都将销毁当前函数的局部变量,因此这些局部变量的销毁顺序与创建顺序相反。

#include <iostream>
using namespace std;

class A{
public:
    A(){cout << "A()" << endl;}
    ~A(){cout << "~A()" << endl;}
};

class B{
public:
    B(){cout << "B()" << endl;}
    ~B(){cout << "~B()" << endl;}
};

void FunA(){
    cout << "FunA()开始" << endl;
    A a;
    B b;
    cout << "throw up a Exception in FunA()!" << endl;
    throw "throwing in FunA()";
}

void FunB(){
    try{
        cout << "FunB() try开始" << endl;
        A a;
        B b;
        FunA();
        cout << "FunB() try结束" << endl;
    }catch (const char* exp){
        cout << "FuncB: Caught exception: " << exp << endl;
        cout << "Handled it, will not throw to caller" << endl;
    }
}

int main(){
    try{
        FunB();
    }catch(const char* exp)
    {
        cout << "Exception: " << exp << endl;
    }
    cout << "main(): exiting gracefully" << endl;
    return 0;
}
输出结果:
FunB() try开始                             
A()                                        
B()                                        
FunA()开始                                 
A()                                        
B()                                        
throw up a Exception in FunA()!            
~B()                                       
~A()                                       
~B()                                       
~A()                                       
FuncB: Caught exception: throwing in FunA()
Handled it, will not throw to caller       
main(): exiting gracefully                 

main( )中调用了FunB( ),而FunB ()调用了FunA( )。在FunA( )中抛出了异常,因为在FunA( )中没有 catch( )块,所以有调用了FunA( )的FunB( )的catch( )块处理。注意到 FuncB( )认为这种异常不严重,没有继续将其传播给 main( )。因此,在 main( )看来,就像没有问题发生一样。

输出指出了对象的创建顺序(与实例化它们的代码的排列顺序相同),还指出了引发异常后对象被销毁的顺序(与实例化顺序相反)。

28.5 std::exception 类

下述重要异常类都是从 std::exception 派生而来的。

  • bad_alloc:使用 new 请求内存失败时引发。
  • bad_cast:试图使用 dynamic_cast 转换错误类型(没有继承关系的类型)时引发。
  • ios_base::failure:由 iostream 库中的函数和方法引发。
    std::exception 类是异常基类,它定义了虚方法 what( );这个方法很有用且非常重要,详细地描述了导致异常的原因。
void SomeFunc()
{
 try
 {
 	// code made exception safe
 }
 catch (const std::exception& exp) // catch bad_alloc, bad_cast, etc
 {
 	cout << "Exception encountered: " << exp.what() << endl;
 }
} 

28.6 从 std::exception 派生出自定义异常类

可以引发所需的任何异常。然而,让自定义异常继承 std::exception 的好处在于,现有的异常处理程序 catch(const std::exception&)不但能捕获 bad_alloc、bad_cast 等异常,还能捕获自定义异常,因为它们的基类都是 exception。

#include <iostream>
#include <exception>
using namespace std;

class MyException : public exception{
private:
    string reason;
public:
    MyException(const char* why):reason(why){}

    virtual const char* what() const throw(){
        return reason.c_str();
    }
};

double Divide(double dividend, double divisor) {
    if(divisor == 0)
        throw MyException("MyException: Dividing by 0 is a crime");
    return dividend/divisor;
}

int main(){
    try{
        double i = 100.100;
        double j = 0;
        Divide(i,j);
    }catch (exception& e){
        cout << e.what() << endl;
    }

    return 0;
}

输出结果:
MyException: Dividing by 0 is a crime

【注意】
请注意程序清单 28.5 中虚方法 CustomException::what( )的声明(如第 13 行所示):
virtual const char* what() const throw()
它以 throw( )结尾,这意味着这个函数本身不会引发异常。这是对异常类的一个重要约束,如果您在该函数中包含一条 throw 语句,编译器将发出警告。如果函数以 throw(int)结尾,意味着该函数可能引发类型为 int 的异常。

第 29 章 继续前行

29.1 当今的处理器

以前,每隔一段时间,处理器的速度都有极大的提高,C++应用程序的速度也得到了极大提高。当时大家都采取伺机而动的策略,利用改进的硬件性能来提高软件的响应速度。虽然当今的处理器越来越快,但真正的创新在于处理器包含的内核数。编写本书期间,即便是普通智能手机都装备了 4 核的 64 位处理器,其处理能力超过了 10 年前的台式机。

可将多核处理器视为一块包含多个处理器的芯片。这些处理器并行地运行,每个处理器都有独立的一级缓存,能够彼此独立地工作。

处理器速度越快,应用程序的性能越高,这合乎逻辑。多核处理器对应用程序性能有何帮助呢?显然,每个内核都能并行地运行应用程序,但这并不一定能提高应用程序的速度,除非您通过编程来利用这种新能力。本书前面介绍的 C++应用程序都是单线程的,不能充分利用多核处理能力。这些应用程序运行在一个线程中,因此只能利用一个内核。

29.2 如何更好地利用多个内核

关键在于创建多线程应用程序。所有的线程都并行地运行,操作系统可让它们在多个内核中运行。详细讨论线程和多线程技术超出了本书的范围,这里只简要地介绍这个主题。

29.2.1 线程是什么

应用程序代码总是运行在线程中。线程是一个同步执行实体,其中的语句依次执行。可将 main( )的代码视为在应用程序的主线程中执行。在这个主线程中,可以创建并行运行的线程。如果应用程序除主线程外,还包含一个或多个并行运行的线程,则被称为多线程应用程序。
线程的创建方式由操作系统决定,可直接调用操作系统提供的 API 来创建线程。

【注意】
从 C++11 起,C++规定由线程函数负责为您调用操作系统 API,这提高了多线程应用程序的可移植性。
创建线程的方式随操作系统而异,C++在头文件中提供了 std::thread,它隐藏了与平台相关的细节。

29.2.2 为何要编写多线程应用程序

使用多线程技术的应用程序并行地执行特定任务的多个会话(session)。假设有 10000 名用户在Amazon 购物,您是其中的一员。Amazon 的 Web 服务器当然不会让其他 9999 位用户都等待,而是创建多个同时为用户服务的线程。如果该 Web 服务器运行在多核处理器或多处理器云上,这些线程将能够充分利用基础设施,向用户提供最佳的性能。

另一个常见的多线程示例是,与用户交互(例如,通过进度条)的同时做其他工作的应用程序。这样的应用程序通常包含用户界面线程和工作线程,其中前者负责显示和更新用户界面以及接受用户输入,而后者在后台完成其任务。磁盘碎片整理工具就是一个这样的应用程序。用户单击“开始”按钮后,将创建一个工作线程,负责扫描和整理磁盘碎片;与此同时,用户界面线程将显示进度,并提供取消碎片整理的选项。为了让用户界面线程显示进度,整理碎片的工作线程需要定期地提供进度;同样,为了让工作线程在用户撤销时停止工作,用户界面线程需要提供这种信息。

【注意】

多线程应用程序常常要求线程彼此通信,这样应用程序才能成为一个整体,而不是一系 列互不关心、各自为政的线程。
另外,顺序也很重要,您不希望用户界面线程在负责整理碎片的工作线程之前结束。在有些情况下,一个线程需要等待另一个线程。例如,读取数据库的线程应等待写入数据 库的线程结束。 让一个线程等待另一个线程被称为线程同步。

29.2.3 线程如何交换数据

线程可共享变量,可访问全局数据。创建线程时,可给它提供一个指向共享对象(结构或类)的指针,如图所示。
在这里插入图片描述
线程将数据写入其他线程能够存取的内存单元,这让线程能够共享数据,从而彼此进行通信。在磁盘碎片整理工具中,工作线程知道进度,而用户界面线程需要获悉这种信息;工作线程定期地存储进度(用整数表示的百分比),而用户界面线程可使用它来显示进度。

这种情形非常简单:一个线程创建信息,另一个线程使用它。如果多个线程读写相同的内存单元,结果将如何呢?有些线程开始读取数据时,其他线程可能还未结束写入操作,这将给数据的完整性带来威胁。这就是需要同步线程的原因所在。

29.2.4 使用互斥量和信号量同步线程

线程是操作系统级实体,而用来同步线程的对象也是操作系统提供的。大多数操作系统都提供了信号量(semaphore)和互斥量(mutex),供您用来同步线程。

互斥量(互斥同步对象)通常用于避免多个线程同时访问同一段代码。换句话说,互斥量指定了一段代码,其他线程要执行它,必须等待当前执行它的线程结束并释放该互斥量。接下来,下一个线程获取该互斥量,完成其工作,并释放该互斥量。从 C++11 起,C++通过类 std::mutex 提供了一种互斥量实现,这个类位于头文件中。

通过使用信号量,可指定多少个线程可同时执行某个代码段。只允许一个线程访问的信号量被称为二值信号量(binary semaphore)。

29.2.5 多线程技术带来的问题

要使用多线程技术,必须妥善地同步线程,否则,您将有大量的无眠之夜。多线程应用程序面临的问题很多,下面是最常见的两个。

  • 竞争状态:多个线程试图写入同一项数据。哪个线程获胜?该对象处于什么状态?
  • 死锁:两个线程彼此等待对方结束,导致它们都处于“等待”状态,而应用程序被挂起。
    妥善地同步可避免竞争状态。一般而言,线程被允许写入共享对象时,您必须格外小心,确保: 每次只能有一个线程写入; 在当前执行写入的线程结束前,不允许其他线程读取该对象。

通过确保任何情况下都不会有两个线程彼此等待,可避免死锁。为此,可使用主线程同步工作线程,也可在线程之间分配任务时,确保工作负荷分配明确。可以让一个线程等待另一个线程,但绝不要同时让后者也等待前者。

29.3 编写杰出的 C++代码

相比于面世之日,C++发生了巨大变化,主要的编译器厂商在标准化方面做出了巨大努力,还有大量工具和函数,这些都有助于编写简洁的 C++代码。编写可靠且易于理解的 C++应用程序真的很容易。下面的一些最佳实践可帮助您创建优质的 C++应用程序。

  • 给变量指定(无论是对您还是其他人来说都)有意义的名称。值得多花点时间给变量取个好名。
  • 对于 int、float 等变量,务必进行初始化。
  • 务必将指针初始化为 NULL 或有效的地址—如运算符 new 返回的地址。
  • 使用数组时,绝不要跨越其边界。跨越数组边界被称为缓冲区溢出,可导致安全漏洞。
  • 不要使用字符串缓冲区(char*),也不要使用 strelen( )和 strcopy( )等函数。std::string 更安全,还提供了很多有用的方法,如获取长度、进行复制和附加的方法。
  • 仅当确定要包含的元素数时才使用静态数组。如果不确定,应使用 std::vector 等动态数组。
  • 声明和定义接受非 POD 类型作为输入的函数时,应考虑将参数声明为引用,以免调用函数时执行不必要的复制步骤。
  • 如果类包含原始指针成员,务必考虑如何在复制或赋值时管理内存资源所有权,即应考虑编写复制构造函数和赋值运算符
  • 编写管理动态数组的实用类时,务必实现移动构造函数和移动赋值运算符,以改善性能。
  • 务必正确地使用 const。理想情况下,get( )函数不应修改类成员,因此应将其声明为 const 函数。同样,除非要修改函数参数包含的值,否则应将其声明为 const 引用。
  • 不要使用原始指针,而应尽可能使用合适的智能指针。
  • 编写实用类时,务必花精力实现让它使用起来更容易的运算符。
  • 在有选择余地的情况下,务必使用模板而不是宏。模板不但是通用的,还是类型安全的。
  • 编写类时,如果其对象将存储在诸如 vector 和 list 等容器中,或者被用作映射中的键,务必实现运算符<,它将用作默认排序标准。
  • 如果您编写的 lambda 表达式很长,应考虑转而使用函数对象,即实现了 operator( )的类,因为函数对象可重用,且只有一个地方需要维护。
  • 绝不要认为运算符 new 肯定会成功。对于分配资源的代码,务必处理其可能引发的异常,即将其放在 try 块中,并编写相应的 catch( )块。
  • 绝不要在析构函数中引发异常。

29.4 更深入地学习 C++

29.4.1 在线文档

要更详细地了解 STL 容器及其方法、算法和功能,可利用在线资源和文档,其中一个这样的流行网站是 http://www.cppreference.com/

29.5.2 提供指南和帮助的社区

有很多活跃的 C++社区。在 StackOverflow(www.StackOverflow.com)、CodeGuru(www.CodeGuru.com)或 CodeProject(www.CodeProject.com)网站注册后,您就可询问自己遇到的技术问题,并获得社区的帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值