C++学习笔记

文章目录

  • 打印
  • 左值(Lvalues)和右值(Rvalues)
  • 类型限定符
  • C++ 存储类
    • auto 存储类
    • mutable 存储类
    • thread_local 存储类
  • 循环
    • 基于范围的for循环(C++11)
  • 函数
    • 匿名函数与表达式(Lambda 函数)(Lambda 表达式)
      • 示例
    • Lambda函数捕获列表
      • 示例
  • C++ 引用
    • C++ 引用 vs 指针
    • C++ 中创建引用
    • C++ 把引用作为参数
    • C++ 把引用作为返回值
  • C++ 基本的输入输出
    • I/O 库头文件
  • C++ 类 & 对象
    • C++ 类定义
    • 成员函数定义
    • 定义 C++ 对象
    • C++ 类成员函数
    • C++ 方法调用成员
    • C++ 类访问修饰符
      • 公有(public)成员
      • 私有(private)成员
      • protected(受保护)成员
    • 继承中的特点
  • C++ 类构造函数 & 析构函数 & 其他函数
    • 类的构造函数
      • 带参数的构造函数
      • 使用初始化列表来初始化字段
    • 类的析构函数
    • C++ 拷贝构造函数
    • C++ 友元函数
    • C++ 内联函数
  • C++ this 指针
  • C++ 类的静态成员
    • 静态成员函数
    • 静态成员函数与普通成员函数的区别:
  • C++ 继承
    • 基类 & 派生类
    • 访问控制和继承
    • 继承类型
    • 多继承
  • C++ 重载运算符和重载函数
    • C++ 中的函数重载
    • C++ 中的运算符重载
  • C++ 多态
    • 虚函数
    • 纯虚函数
  • C++ 文件和流
    • fstream标准库
    • 打开文件
    • 关闭文件
    • 写入文件
    • 读取文件
    • 文件位置指针
  • C++ 异常处理
    • 抛出异常
    • 捕获异常
    • C++ 标准的异常
    • 定义新的异常
  • C++ 动态内存
    • new 和 delete 运算符
    • 数组的动态内存分配
    • 对象的动态内存分配
  • C++ 命名空间
    • 定义命名空间
    • using 指令
    • 不连续的命名空间
    • 嵌套的命名空间
  • C++ 模板
    • 函数模板
    • 类模板
  • C++ 预处理器
    • #define 预处理
    • 参数宏
    • 条件编译
    • \# 和 ## 运算符
    • C++ 中的预定义宏
  • C++ 信号处理
    • signal() 函数
    • raise() 函数
  • C++ 多线程
    • 创建线程
    • 终止线程
  • 连接和分离线程
  • C++ STL
  • C++ 标准库

打印

using namespace std;

cout << "date" << endl;
endl会自动添加换行.
或
cout << "date\n";

int g = 10;
cout << g;
或
cout << g endl;

如果添加using namespace std;
需要
std::cout << “date” << std::endl;

cout << “int i :” << i << endl;

左值(Lvalues)和右值(Rvalues)

  • 左值(lvalue):指向内存位置的表达式被称为左值(lvalue)表达式。左值可以出现在赋值号的左边或右边。
  • 右值(rvalue):术语右值(rvalue)指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式,也就是说,右值可以出现在赋值号的右边,但不能出现在赋值号的左边。
  • 变量是左值,因此可以出现在赋值号的左边。数值型的字面值是右值,因此不能被赋值,不能出现在赋值号的左边。

类型限定符

限定符 含义
const const 类型的对象在程序执行期间不能被修改改变。
volatile 修饰符 volatile 告诉编译器不需要优化volatile声明的变量,让程序可以直接从内存中读取变量。对于一般的变量编译器会对变量进行优化,将内存中的变量值放在寄存器中以加快读写效率。
restrict 由 restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。

C++ 存储类

  • auto
  • register (C++17弃用)
  • static
  • extern
  • mutable
  • thread_local (C++11增加)

auto 存储类

  • auto 关键字用于两种情况:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。
  • 根据初始化表达式自动推断被声明的变量的类型,如:
auto f=3.14;      //double
auto s("hello");  //const char*
auto z = new auto(9); // int*
auto x1 = 5, x2 = 5.0, x3='r';//错误,必须是初始化为同一类型

mutable 存储类

  • mutable 说明符仅适用于类的对象,它允许对象的成员替代常量。也就是说,mutable 成员可以通过 const 成员函数修改。

thread_local 存储类

  • 使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。

  • thread_local 说明符可以与 static 或 extern 合并。

  • 可以将 thread_local 应用于数据声明和定义,thread_local 不能用于函数声明或定义。

循环

基于范围的for循环(C++11)

int my_array[5] = {
     1, 2, 3, 4, 5};
// 每个数组元素乘于 2
for (int &x : my_array)
{
     
    cout << x << endl;  
}
// auto 类型也是 C++11 新标准中的,用来自动获取变量的类型
for (auto &x : my_array) {
     
    cout << x << endl;  
}

函数

匿名函数与表达式(Lambda 函数)(Lambda 表达式)

  • Lambda 表达式把函数看作对象。Lambda 表达式可以像对象一样使用,比如可以将它们赋给变量和作为参数传递,还可以像函数一样对其求值。

  • Lambda 表达式本质上与函数声明非常类似。

[ captures list] ( params ) -> ret {
      body }
[ 捕获列表 ] ( 参数列表 ) ->( 返回值类型){
      函数体 }
其中参数列表和返回类型是可以省略的

**[captures] (params) -> return_type { statments;} **

  • [captures]为捕获列表,用于捕获外层变量
  • (params)为匿名函数参数列表
  • ->return_type指定匿名函数返回值类型
  • { statments; }部分为函数体,包括一系列语句
  • 当匿名函数没有参数时,可以省略(params)部分
  • 当匿名函数体的返回值只有一个类型或者返回值为void时,可以省略->return_type部分
  • 定义匿名函数时,一般使用auto作为匿名函数类型

示例

auto func1 = [](int x, int y) -> int {
      return x + y; }; 
auto func2 = [](int x, int y) {
      return x > y; }; // 省略返回值类型
auto func3 = [] {
      global_ip = 0; }; // 省略参数部分
auto f = []{
     return 42;};

Lambda函数捕获列表

  • 为了能够在Lambda函数中使用外部作用域中的变量,需要在[]中指定使用哪些变量。
  • [] 不捕获任何变量
  • [&] 捕获外部作用域中所有变量,并作为引用在匿名函数体中使用
  • [=] 捕获外部作用域中所有变量,并拷贝一份在匿名函数体中使用
  • [x, &y] x按值捕获, y按引用捕获
  • [&, x] x按值捕获. 其它变量按引用捕获
  • [=, &y] y按引用捕获. 其它变量按值捕获
  • [this] 捕获当前类中的this指针,如果已经使用了&或者=就默认添加此选项

示例

auto lambda_func_sum = [](int x, int y) {
      return x + y; }; // 定义lambda函数
void (*func_ptr)(int, int) = lambda_func_sum; // 将lambda函数赋值给函数指针
func_ptr(10, 20);  // 调用函数指针

C++ 引用

C++ 引用 vs 指针

引用很容易与指针混淆,它们之间有三个主要的不同:

  • 不存在空引用。引用必须连接到一块合法的内存。
  • 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
  • 引用必须在创建时被初始化。指针可以在任何时间被初始化。

C++ 中创建引用

int&  r = i;
double& s = d;
  • & 读作引用。因此,第一个声明可以读作 “r 是一个初始化为 i 的整型引用”,第二个声明可以读作 “s 是一个初始化为 d 的 double 型引用”。

C++ 把引用作为参数

  • void swap(int& x, int& y);

C++ 把引用作为返回值

double vals[] = {
     10.1, 12.6, 33.1, 24.1, 50.0};
double& setValues(int i) {
       
   double& ref = vals[i];    
   return ref;   // 返回第 i 个元素的引用,ref 是一个引用变量,ref 引用 vals[i],最后再返回 shit。 
}

// 在调用函数中使用
setValues(1) = 20.23; // 改变第 2 个元素
setValues(3) = 70.8;  // 改变第 4 个元素

C++ 基本的输入输出

I/O 库头文件

头文件 函数和描述
该文件定义了 cin、cout、cerr 和 clog 对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流。
该文件通过所谓的参数化的流操纵器(比如 setw 和 setprecision),来声明对执行标准化 I/O 有用的服务。
该文件为用户控制的文件处理声明服务。我们将在文件和流的相关章节讨论它的细节。
  • C++ 编译器根据要输出变量的数据类型,选择合适的流插入运算符来显示值。<< 运算符被重载来输出内置类型(整型、浮点型、double 型、字符串和指针)的数据项。
  • C++ 编译器根据要输入值的数据类型,选择合适的流提取运算符来提取值,并把它存储在给定的变量中。
  • 流提取运算符 >> 在一个语句中可以多次使用,如果要求输入多个数据,可以使用如下语句:
cin >> name >> age;
  • 这相当于下面两个语句:
cin >> name;
cin >> age;

C++ 类 & 对象

C++ 类定义

class classname {
     
    public:
    private:
    protected:
        int var;
        int func();
};

关键字 类名 {
     
    访问修饰符:
        变量;
        方法;
};

成员函数定义

  • :: 是范围解析运算符
int classname::func();

定义 C++ 对象

classname varname;

C++ 类成员函数

  • 类的成员函数是指那些把定义和原型写在类定义内部的函数。
  • 类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。

C++ 方法调用成员

  • 类方法或函数实现中调用本类的成员变量不需要进行声明可直接使用。
int classname::func(int var) {
     
    classvar = var;
}

C++ 类访问修饰符

  • 类成员的访问限制是通过在类主体内部对各个区域标记 public、private、protected 来指定的。
  • 关键字 public、private、protected 称为访问修饰符。
  • 一个类可以有多个 public、protected 或 private 标记区域。
  • 每个标记区域在下一个标记区域开始之前或者在遇到类主体结束右括号之前都是有效的。
  • 成员和类的默认访问修饰符是 private。
class Base {
     
 
   public:
 
  // 公有成员
 
   protected:
 
  // 受保护成员
 
   private:
 
  // 私有成员
 
};

公有(public)成员

  • 公有成员在程序中类的外部是可访问的。可以不使用任何成员函数来设置和获取公有变量的值。
classname objectname;
objectname.menber = 10;// 直接引用public修饰的成员

私有(private)成员

  • 私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。
  • 只有类和友元函数可以访问私有成员。
  • 默认情况下,类的所有成员都是私有的。

protected(受保护)成员

  • protected(受保护)成员变量或函数与私有成员十分相似,但有一点不同,protected(受保护)成员在派生类(即子类)中是可访问的。

继承中的特点

  • 有public, protected, private三种继承方式,它们相应地改变了基类成员的访问属性。
  • 1.public 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:public, protected, private
  • 2.protected 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:protected, protected, private
  • 3.private 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:private, private, private
  • 但无论哪种继承方式,以下两点都没有改变:
  • private 成员只能被本类成员(类内)和友元访问,不能被派生类访问;
  • protected 成员可以被派生类访问。
继承方式 基类的public成员 基类的protected成员 基类的private成员 继承引起的访问控制关系变化概括
public继承 仍为public成员 仍为protected成员 不可见 基类的非私有成员在子类的访问属性不变
protected继承 变为protected成员 变为protected成员 不可见 基类的非私有成员都为子类的保护成员
private继承 变为private成员 变为private成员 不可见 基类中的非私有成员都称为子类的私有成员

C++ 类构造函数 & 析构函数 & 其他函数

类的构造函数

  • 类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。
  • 构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。
  • 构造函数可用于为某些成员变量设置初始值。
classname::classname(void){
     };

带参数的构造函数

  • 默认的构造函数没有任何参数
  • 构造函数参数在创建对象时传入
  • 在定义构造函数时声明参数
classname::classname(int var){
     };

使用初始化列表来初始化字段

  • 假设有一个类 C,具有多个字段 X、Y、Z 等需要进行初始化
C::C( double a, double b, double c): X(a), Y(b), Z(c)
{
     
  ....
}

类的析构函数

  • 类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
  • 析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。
  • 析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
classname::~classname(void){
     };

C++ 拷贝构造函数

  • 拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:
  • 通过使用另一个同类型的对象来初始化新创建的对象。
  • 复制对象把它作为参数传递给函数。
  • 复制对象,并从函数返回这个对象。
  • 如果在类中没有定义拷贝构造函数,编译器会自行定义一个。
  • 如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。
classname (const classname &obj) {
     
   // 构造函数的主体
}

int main() {
     
    classname obj1;
    classname obj2 = obj1; // 此处会调用拷贝构造函数,将obj1作为参数调用obj2的拷贝构造函数   
}

C++ 友元函数

  • 类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。
  • 友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
  • 友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
  • 如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend。
class Box
{
     
   double width;
public:
   double length;
   friend void printWidth( Box box );
   void setWidth( double wid );
};
  • 声明类 ClassTwo 的所有成员函数作为类 ClassOne 的友元,需要在类 ClassOne 的定义中放置声明:friend class ClassTwo;

C++ 内联函数

  • 如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。
  • 对内联函数进行任何修改,都需要重新编译函数的所有客户端。
  • 如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline。

C++ this 指针

  • 在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。
  • this 指针是所有成员函数的隐含参数。
  • 它可以用来指向调用对象。
  • 友元函数没有 this 指针,因为友元不是类的成员。
  • 只有成员函数才有 this 指针。

C++ 类的静态成员

  • 当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。
  • 静态成员在类的所有对象中是共享的。
  • 如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。
  • 不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化。
class classname {
     
    public:
        static int var;
};

classname::var = 0;

静态成员函数

  • 静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。
  • 静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。
  • 静态成员函数有一个类范围,他们不能访问类的 this 指针。
  • 可以使用静态成员函数来判断类的某些对象是否已被创建。

静态成员函数与普通成员函数的区别:

  • 静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
  • 普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针。
class classname {
     
    public:
        static int var;
        static int staticfun() {
     
            return var;
        }
};
classname::staticfun();

C++ 继承

  • 继承允许我们依据另一个类来定义一个类。
  • 当创建一个类时,不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
class derived-class:access-specifier base-class {
     };
class classname:public fatherclass {
     

};
// 基类
class Animal {
     
    void eat();
    void sleep();
};

//派生类
class Dog : public Animal {
     
    void bark();
};

基类 & 派生类

  • 一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数。定义一个派生类,我们使用一个类派生列表来指定基类。
class derived-class: access-specifier base-class {
     };
  • 其中,访问修饰符 access-specifier 是 public、protected 或 private 其中的一个。
  • base-class 是之前定义过的某个类的名称。如果未使用访问修饰符 access-specifier,则默认为 private。

访问控制和继承

  • 派生类可以访问基类中所有的非私有成员。
访问 public protected private
同一个类 yes yes yes
派生类 yes yes no
外部的类 yes no no
  • 一个派生类继承了所有的基类方法,但下列情况除外:
  1. 基类的构造函数、析构函数和拷贝构造函数。
  2. 基类的重载运算符。
  3. 基类的友元函数。

继承类型

  • 当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。
  • 几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:
  • 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
  • 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
  • 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。

多继承

  • 多继承即一个子类可以有多个父类,它继承了多个父类的特性。
  • C++ 类可以从多个类继承成员。
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,{
     
    <派生类类体>
};

C++ 重载运算符和重载函数

  • C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。
  • 重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同。
  • 调用一个重载函数或重载运算符时,编译器通过把您所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。
  • 选择最合适的重载函数或重载运算符的过程,称为重载决策。

C++ 中的函数重载

  • 在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。
  • 能仅通过返回类型的不同来重载函数。

C++ 中的运算符重载

  • 可以重定义或重载大部分 C++ 内置的运算符。

  • 重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。

  • 重载运算符有一个返回类型和一个参数列表。

  • …过于复杂,暂时略

C++ 多态

  • 多态按字面的意思就是多种形态。
  • 当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
  • C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
  • 静态多态,或静态链接 ,函数调用在程序执行前就准备好了,有时候这也被称为早绑定。

虚函数

  • 虚函数 是在基类中使用关键字 virtual 声明的函数。
  • 在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
  • 在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
  • 一个有虚函数的类创建的对象可以调用虚函数实体。
  • 一个继承基类存在虚函数的类创建的对象会调用重载的虚函数。
class classname {
     
    public:
        virtual int func() {
      };
};

纯虚函数

  • 您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。
  • 一个类如果有纯虚函数,那么其子类必须重载纯虚函数。
class classname {
     
    public:
        virtual int func() = 0;
};

C++ 文件和流

  • 要在 C++ 中进行文件处理,必须在 C++ 源代码文件中包含头文件 和 。

fstream标准库

数据类型 描述
ofstream 该数据类型表示输出文件流,用于创建文件并向文件写入信息。
ifstream 该数据类型表示输入文件流,用于从文件读取信息。
fstream 该数据类型通常表示文件流,且同时具有 ofstream 和 ifstream 两种功能,这意味着它可以创建文件,向文件写入信息,从文件读取信息。

打开文件

  • ofstream 和 fstream 对象都可以用来打开文件进行写操作,如果只需要打开文件进行读操作,则使用 ifstream 对象。
  • open() 函数是 fstream、ifstream 和 ofstream 对象的一个成员。
  • open() 成员函数的第一参数指定要打开的文件的名称和位置,第二个参数定义文件被打开的模式。
void open(const char *filename, ios::openmode mode);
模式标志 描述
ios::app 追加模式。所有写入都追加到文件末尾。
ios::ate 文件打开后定位到文件末尾。
ios::in 打开文件用于读取。
ios::out 打开文件用于写入。
ios::trunc 如果该文件已经存在,其内容将在打开文件之前被截断,即把文件长度设为 0。
ofstream outfile;
outfile.open("file.dat", ios::out | ios::trunc );

ifstream  afile;
afile.open("file.dat", ios::out | ios::in );

关闭文件

  • close() 函数是 fstream、ifstream 和 ofstream 对象的一个成员。
ofstream outfile;
outfile.close();

写入文件

  • 在 C++ 编程中,我们使用流插入运算符( << )向文件写入信息,就像使用该运算符输出信息到屏幕上一样。

读取文件

  • 在 C++ 编程中,我们使用流提取运算符( >> )从文件读取信息,就像使用该运算符从键盘输入信息一样。

文件位置指针

  • istream 和 ostream 都提供了用于重新定位文件位置指针的成员函数。这些成员函数包括关于 istream 的 seekg(“seek get”)和关于 ostream 的 seekp(“seek put”)。
  • seekg 和 seekp 的参数通常是一个长整型。
  • 第二个参数可以用于指定查找方向。查找方向可以是 ios::beg(默认的,从流的开头开始定位),也可以是 ios::cur(从流的当前位置开始定位),也可以是 ios::end(从流的末尾开始定位)。
  • 文件位置指针是一个整数值,指定了从文件的起始位置到指针所在位置的字节数。
// 定位到 fileObject 的第 n 个字节(假设是 ios::beg)
fileObject.seekg( n );
 
// 把文件的读指针从 fileObject 当前位置向后移 n 个字节
fileObject.seekg( n, ios::cur );
 
// 把文件的读指针从 fileObject 末尾往回移 n 个字节
fileObject.seekg( n, ios::end );
 
// 定位到 fileObject 的末尾
fileObject.seekg( 0, ios::end );

C++ 异常处理

  • 异常是程序在执行期间产生的问题。C++ 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。
  • 异常提供了一种转移程序控制权的方式。C++ 异常处理涉及到三个关键字:try、catch、throw。
  • throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
  • catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
  • try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。
  • 如果有一个块抛出一个异常,捕获异常的方法会使用 try 和 catch 关键字。
  • 如果 try 块在不同的情境下会抛出不同的异常,这个时候可以尝试罗列多个 catch 语句,用于捕获不同类型的异常。
  • try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码。
try
{
     
   // 保护代码
}catch( ExceptionName e1 )
{
     
   // catch 块
}catch( ExceptionName e2 )
{
     
   // catch 块
}catch( ExceptionName eN )
{
     
   // catch 块
}

抛出异常

  • 使用 throw 语句在代码块中的任何地方抛出异常。
  • throw 语句的操作数可以是任意的表达式,表达式的结果的类型决定了抛出的异常的类型。
double division(int a, int b)
{
     
   if( b == 0 )
   {
     
      throw "Division by zero condition!";
   }
   return (a/b);
}

捕获异常

  • catch 块跟在 try 块后面,用于捕获异常。
  • 可以指定想要捕捉的异常类型,这是由 catch 关键字后的括号内的异常声明决定的。
  • 如果想让 catch 块能够处理 try 块抛出的任何类型的异常,则必须在异常声明的括号内使用省略号 …
try
{
     
   // 保护代码
}catch( ExceptionName e )
{
     
  // 捕获一个类型为 ExceptionName 的异常。
  // 处理 ExceptionName 异常的代码
}

try
{
     
   // 保护代码
}catch(...)
{
     
  // 能处理任何异常的代码
}
#include 
using namespace std;
 
double division(int a, int b)
{
     
   if( b == 0 )
   {
     
      throw "Division by zero condition!";
   }
   return (a/b);
}
 
int main ()
{
     
   int x = 50;
   int y = 0;
   double z = 0;
 
   try {
     
     z = division(x, y);
     cout << z << endl;
   }catch (const char* msg) {
     
     cerr << msg << endl;
   }
 
   return 0;
}

C++ 标准的异常

  • C++ 提供了一系列标准的异常,定义在 中,我们可以在程序中使用这些标准的异常。

各个标准异常说明…略

定义新的异常

  • 通过继承和重载 exception 类来定义新的异常。
#include 
#include 
using namespace std;
 
struct MyException : public exception
{
     
  const char * what () const throw ()
  {
     
    return "C++ Exception";
  }
};
 
int main()
{
     
  try
  {
     
    throw MyException();
  }
  catch(MyException& e)
  {
     
    std::cout << "MyException caught" << std::endl;
    std::cout << e.what() << std::endl;
  }
  catch(std::exception& e)
  {
     
    //其他的错误
  }
}

C++ 动态内存

  • 栈:在函数内部声明的所有变量都将占用栈内存。
  • 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。

new 和 delete 运算符

new data-type;
/******************/
double* pvalue  = NULL; // 初始化为 null 的指针
pvalue  = new double;   // 为变量请求内存
  • data-type 可以是包括数组在内的任意内置的数据类型,也可以是包括类或结构在内的用户自定义的任何数据类型。
  • malloc() 函数在 C 语言中就出现了,在 C++ 中仍然存在,但建议尽量不要使用 malloc() 函数。
  • new 与 malloc() 函数相比,其主要的优点是,new 不只是分配了内存,它还创建了对象。
  • 某个已经动态分配内存的变量不再需要使用时,可以使用 delete 操作符释放它所占用的内存。
delete pvalue;        // 释放 pvalue 所指向的内存

数组的动态内存分配

char* pvalue  = NULL;   // 初始化为 null 的指针
pvalue  = new char[20]; // 为变量请求内存

delete [] pvalue;        // 删除 pvalue 所指向的数组
int **array
// 假定数组第一维长度为 m, 第二维长度为 n
// 动态分配空间
array = new int *[m];
for( int i=0; i<m; i++ )
{
     
    array[i] = new int [n]  ;
}
//释放
for( int i=0; i<m; i++ )
{
     
    delete [] array[i];
}
delete [] array;
int ***array;
// 假定数组第一维为 m, 第二维为 n, 第三维为h
// 动态分配空间
array = new int **[m];
for( int i=0; i<m; i++ )
{
     
    array[i] = new int *[n];
    for( int j=0; j<n; j++ )
    {
     
        array[i][j] = new int [h];
    }
}
//释放
for( int i=0; i<m; i++ )
{
     
    for( int j=0; j<n; j++ )
    {
     
        delete[] array[i][j];
    }
    delete[] array[i];
}
delete[] array;

对象的动态内存分配

#include 
using namespace std;
 
class Box
{
     
   public:
      Box() {
      
         cout << "调用构造函数!" <<endl; 
      }
      ~Box() {
      
         cout << "调用析构函数!" <<endl; 
      }
};
 
int main( )
{
     
   Box* myBoxArray = new Box[4];
 
   delete [] myBoxArray; // 删除数组
   return 0;
}

C++ 命名空间

  • 本质上,命名空间就是定义了一个范围。

定义命名空间

  • 命名空间的定义使用关键字 namespace,后跟命名空间的名称
namespace namespace_name {
     
   // 代码声明
}
  • 为了调用带有命名空间的函数或变量,需要在前面加上命名空间的名称
namespace_name ::code;  // code 可以是变量或函数

using 指令

  • 可以使用 using namespace 指令,在使用命名空间时就可以不用在前面加上命名空间的名称。
  • 这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。

不连续的命名空间

  • 命名空间可以定义在几个不同的部分中,因此命名空间是由几个单独定义的部分组成的。
  • 一个命名空间的各个组成部分可以分散在多个文件中。
  • 如果命名空间中的某个组成部分需要请求定义在另一个文件中的名称,则仍然需要声明该名称。
  • 命名空间定义可以是定义一个新的命名空间,也可以是为已有的命名空间增加新的元素。

嵌套的命名空间

  • 命名空间可以嵌套,您可以在一个命名空间中定义另一个命名空间
namespace namespace_name1 {
     
   // 代码声明
   namespace namespace_name2 {
     
      // 代码声明
   }
}
  • 以通过使用 :: 运算符来访问嵌套的命名空间中的成员。
  • 如果使用的是 namespace_name1,那么在该范围内 namespace_name2 中的元素也是可用的
// 访问 namespace_name2 中的成员
using namespace namespace_name1::namespace_name2;
 
// 访问 namespace:name1 中的成员
using namespace namespace_name1;

C++ 模板

  • 模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。
  • 模板是创建泛型类或函数的蓝图或公式。
  • 库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。
  • 每个容器都有一个单一的定义,比如 向量,我们可以定义许多不同类型的向量,比如 vector 或 vector 。
  • 可以使用模板来定义函数和类。

函数模板

template <typename type> ret-type func-name(parameter list)
{
     
   // 函数的主体
}
  • type 是函数所使用的数据类型的占位符名称。这个名称可以在函数定义中使用。
#include 
#include 
 
using namespace std;
 
template <typename T>
inline T const& Max (T const& a, T const& b) 
{
      
    return a < b ? b:a; 
} 
int main ()
{
     
 
    int i = 39;
    int j = 20;
    cout << "Max(i, j): " << Max(i, j) << endl; 
 
    double f1 = 13.5; 
    double f2 = 20.7; 
    cout << "Max(f1, f2): " << Max(f1, f2) << endl; 
 
    string s1 = "Hello"; 
    string s2 = "World"; 
    cout << "Max(s1, s2): " << Max(s1, s2) << endl; 
 
   return 0;
}

类模板

template <class type> class class-name {
     
.
.
.
}
  • type 是占位符类型名称,可以在类被实例化的时候进行指定。
  • 可以使用一个逗号分隔的列表来定义多个泛型数据类型。
#include 
#include 
#include 
#include 
#include 
 
using namespace std;
 
template <class T>
class Stack {
      
  private: 
    vector<T> elems;     // 元素 
 
  public: 
    void push(T const&);  // 入栈
    void pop();               // 出栈
    T top() const;            // 返回栈顶元素
    bool empty() const{
            // 如果为空则返回真。
        return elems.empty(); 
    } 
}; 
 
template <class T>
void Stack<T>::push (T const& elem) 
{
      
    // 追加传入元素的副本
    elems.push_back(elem);    
} 
 
template <class T>
void Stack<T>::pop () 
{
      
    if (elems.empty()) {
      
        throw out_of_range("Stack<>::pop(): empty stack"); 
    }
    // 删除最后一个元素
    elems.pop_back();         
} 
 
template <class T>
T Stack<T>::top () const 
{
      
    if (elems.empty()) {
      
        throw out_of_range("Stack<>::top(): empty stack"); 
    }
    // 返回最后一个元素的副本 
    return elems.back();      
} 
 
int main() 
{
      
    try {
      
        Stack<int>         intStack;  // int 类型的栈 
        Stack<string> stringStack;    // string 类型的栈 
 
        // 操作 int 类型的栈 
        intStack.push(7); 
        cout << intStack.top() <<endl; 
 
        // 操作 string 类型的栈 
        stringStack.push("hello"); 
        cout << stringStack.top() << std::endl; 
        stringStack.pop(); 
        stringStack.pop(); 
    } 
    catch (exception const& ex) {
      
        cerr << "Exception: " << ex.what() <<endl; 
        return -1;
    } 
}

C++ 预处理器

  • 预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。
  • 所有的预处理器指令都是以井号(#)开头,只有空格字符可以出现在预处理指令之前。
  • 预处理指令不是 C++ 语句,所以它们不会以分号(;)结尾。

#define 预处理

#define 预处理指令用于创建符号常量。该符号常量通常称为宏,指令的一般形式是:

#define macro-name replacement-text 

参数宏

  • 可以使用 #define 来定义一个带有参数的宏
#define MIN(a,b) (a

条件编译

  • 有几个指令可以用来有选择地对部分程序源代码进行编译。这个过程被称为条件编译。

# 和 ## 运算符

  • # 和 ## 预处理运算符在 C++ 和 ANSI/ISO C 中都是可用的。
  • # 运算符会把 replacement-text 令牌转换为用引号引起来的字符串。
  • ## 运算符用于连接两个令牌。

C++ 中的预定义宏

描述
LINE 这会在程序编译时包含当前行号。
FILE 这会在程序编译时包含当前文件名。
DATE 这会包含一个形式为 month/day/year 的字符串,它表示把源文件转换为目标代码的日期。
TIME 这会包含一个形式为 hour:minute:second 的字符串,它表示程序被编译的时间。

C++ 信号处理

  • 信号是由操作系统传给进程的中断,会提早终止一个程序。
  • 下表所列信号可以在程序中捕获,并可以基于信号采取适当的动作。
  • 这些信号是定义在 C++ 头文件 中。
信号 描述
SIGABRT 程序的异常终止,如调用 abort。
SIGFPE 错误的算术运算,比如除以零或导致溢出的操作。
SIGILL 检测非法指令。
SIGINT 程序终止(interrupt)信号。
SIGSEGV 非法访问内存。
SIGTERM 发送到程序的终止请求。

signal() 函数

  • C++ 信号处理库提供了 signal 函数,用来捕获突发事件。
  • 这个函数接收两个参数:第一个参数是一个整数,代表了信号的编号;第二个参数是一个指向信号处理函数的指针。
signal(registered signal, signal handler)

raise() 函数

  • 可以使用函数 raise() 生成信号,该函数带有一个整数信号编号作为参数。
  • 在这里,sig 是要发送的信号的编号,这些信号包括:SIGINT、SIGABRT、SIGFPE、SIGILL、SIGSEGV、SIGTERM、SIGHUP。
int raise (signal sig);

C++ 多线程

  • 多线程是多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序。
  • 一般情况下,两种类型的多任务处理:基于进程和基于线程。
  • 基于进程的多任务处理是程序的并发执行。
  • 基于线程的多任务处理是同一程序的片段的并发执行。

创建线程

#include 
pthread_create (thread, attr, start_routine, arg) 
  • 创建线程成功时,函数返回 0,若返回值不为 0 则说明创建线程失败。
参数 描述
thread 指向线程标识符指针。
attr 一个不透明的属性对象,可以被用来设置线程属性。您可以指定线程属性对象,也可以使用默认值 NULL。
start_routine 线程运行函数起始地址,一旦线程被创建就会执行。
arg 运行函数的参数。它必须通过把引用作为指针强制转换为 void 类型进行传递。如果没有传递参数,则使用 NULL。

终止线程

  • pthread_exit 用于显式地退出一个线程。
  • 通常情况下,pthread_exit() 函数是在线程完成工作后无需继续存在时被调用。
  • 如果 main() 是在它所创建的线程之前结束,并通过 pthread_exit() 退出,那么其他线程将继续执行。否则,它们将在 main() 结束时自动被终止。
#include 
pthread_exit (status) 
//等各个线程退出后,进程才结束,否则进程强制结束了,线程可能还没反应过来;
pthread_exit(NULL);

连接和分离线程

pthread_join (threadid, status) 
pthread_detach (threadid) 
  • pthread_join() 子程序阻碍调用程序,直到指定的 threadid 线程终止为止。
  • 当创建一个线程时,它的某个属性会定义它是否是可连接的(joinable)或可分离的(detached)。
  • 只有创建时定义为可连接的线程才可以被连接。
  • 如果线程创建时被定义为可分离的,则它永远也不能被连接。
  • c++ 11 之后有了标准的线程库

C++ STL

C++ 标准库

你可能感兴趣的:(学习笔记,c++)