C++知识回顾(进阶篇)

目录

  1. 1. 一、C++类与对象
    1. 1.1. C++类的定义
    2. 1.2. 定义C++对象
    3. 1.3. 访问数据成员
    4. 1.4. 成员函数
      1. 1.4.1. 内部定义
      2. 1.4.2. 外部定义,使用范围解析运算符::
      3. 1.4.3. 成员函数的调用
    5. 1.5. C++类访问修饰符
      1. 1.5.1. 公有(public)成员
      2. 1.5.2. 私有(private)成员
      3. 1.5.3. 保护(protected)成员
    6. 1.6. C++类构造函数与析构函数
      1. 1.6.1. 类的构造函数
        1. 1.6.1.1. 构造函数带参数
        2. 1.6.1.2. 使用初始化列表来初始化字段
    7. 1.7. C++拷贝构造函数
    8. 1.8. 友元函数
    9. 1.9. C++ this指针
    10. 1.10. C++指向类的指针
  2. 2. 二、C++继承
    1. 2.1. 基类与派生类
    2. 2.2. 访问控制和继承
    3. 2.3. 继承类型
    4. 2.4. 多继承
      1. 2.4.1. 环状继承
  3. 3. 三、C++重载
    1. 3.1. C++中的函数重载
    2. 3.2. C++中的运算符重载
  4. 4. 四、C++ 多态
    1. 4.1. 虚函数
    2. 4.2. 纯虚函数
  5. 5. 五、C++数据抽象
  6. 6. 六、C++ 数据封装
  7. 7. 七、C++ 接口(抽象类)
  8. 8. 八、C++文件和流
    1. 8.1. 打开文件
    2. 8.2. 关闭文件
    3. 8.3. 读取&写入实例
    4. 8.4. 文件位置指针
  9. 9. 九、C++异常处理
    1. 9.1. 抛出异常
    2. 9.2. 捕获异常
    3. 9.3. C++标准的异常
  10. 10. 十、C++动态内存
    1. 10.1. new 和 delete 运算符
    2. 10.2. 数组的动态内存分配
      1. 10.2.1. 一维数组
      2. 10.2.2. 二维数组
      3. 10.2.3. 三维数组
      4. 10.2.4. 对象的动态内存分配
  11. 11. 十一、C++命名空间
    1. 11.1. 定义命名空间
    2. 11.2. using 指令
    3. 11.3. 不连续的命名空间
    4. 11.4. 嵌套的命名空间
    5. 11.5. 补充
  12. 12. 十二、重要知识点:C++模板
    1. 12.1. 函数模板
    2. 12.2. 类模板
  13. 13. 十三、C++预处理器
    1. 13.1. #define预处理
    2. 13.2. 参数宏
    3. 13.3. 条件编译
    4. 13.4. #和## 运算符
  14. 14. 十四、C++信号处理
    1. 14.1. signal()函数
    2. 14.2. raise()函数
  15. 15. 十五、C++ 多线程
    1. 15.1. Linux下面的操作
      1. 15.1.1. 创建线程
      2. 15.1.2. 终止线程
      3. 15.1.3. 创建线程并接受传入的参数
      4. 15.1.4. 向线程传递参数
      5. 15.1.5. 连接和分离线程

C++在C语言的基础之上又开拓了自己的新领地,增加了面向对象编程,C++支持面向对象程序设计,继承和多态是其被人津津乐道的东西。

一、C++类与对象

C++类的定义

定义一个类,本质是定义一个数据类型的蓝图。C语言对数据类型的描述还不够细致,于是设计师加入类和对象
类定义是以class开头..,末尾一定要加;
下面描述一个“砖头”数据类型的基本特征–长宽高。

class Box{
    public:
        double length;
        double width;
        double height;
};                

长宽高就是类Box的类成员,public就是公共成员在类的外部是可以访问的,还有private(私密)、protected。

定义C++对象

将类当普通数据类型(如:int)看待,就很简单了。

Box box1;        //砖头box1
Box box2;        //砖头box2    二者的数据类型都是Box

访问数据成员

类的对象的公共数据成员可以使用直接成员访问运算符(.)来访问,类似结构体访问内部成员,但是结构体和类还是有很大的不同,类功能更强大

class Box{
    public:
        double length;
        double width;
        double height;
};
//---main()内部-------------------
Box box1;
box1.length = 5.0;
box1.width = 6.0;
box1.height = 7.0;

成员函数

这就是与结构体数据形式上的第一个差别。
前面说了,成员变量,这里加入成员函数,只知道砖头的长宽高还是不够,我们还是要知道砖头的使用用途。
在类定义中定义的成员函数把函数声明为内联的,即使没有使用inline标识符

class Box{
    public:
        double length;
        double width;
        double height;
        double getVolume(void);        //返回体积
};

接下来需要继续知道函数的功能。有两种办法,

  1. 在类的内部定义。
  2. 外部定义,使用范围解析运算符::

    内部定义

    class Box{
     public:
         double length;
         double width;
         double height;
         double getVolume(void)
         {
             return length*width*height;
         }
    };

    外部定义,使用范围解析运算符::

class Box{
    public:
        double length;
        double width;
        double height;
        double getVolume(void);
};
double Box:: getVolume(void)
{
    return length*width*height;
}

成员函数的调用

和成员变量的调用一样,采取.的形式。

Box myBox;        //创建一个对象
myBox.getVolume();        //调用成员函数

C++类访问修饰符

数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员(与结构体的第二个区别),有三个区域标记:publicprivateprotected,称为访问控制符

class Base{
    public:
        //共有成员
    protected:
        //受保护成员
    private:
        //私有成员
};

公有(public)成员

共有成员在程序中类的外部是可访问的。您可以不使用任何成员函数来设置和获取公有变量的值。参考代码:访问数据成员

私有(private)成员

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

#include<iostream>
using namespace std;
class Box
{
   public:
      double length;
      void setWidth( double wid );
      double getWidth( void );

   private:
      double width;
};

// 成员函数定义
double Box::getWidth(void)
{
    return width ;
}

void Box::setWidth( double wid )
{
    width = wid;
}

// 程序的主函数
int main( )
{
   Box box;

   // 不使用成员函数设置长度
   box.length = 10.0; // OK: 因为 length 是公有的
   cout << "Length of box : " << box.length <<endl;

   // 不使用成员函数设置宽度
   // box.width = 10.0; // Error: 因为 width 是私有的
   box.setWidth(10.0);  // 使用成员函数设置宽度
   cout << "Width of box : " << box.getWidth() <<endl;

   return 0;
}

保护(protected)成员

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

参考资料:https://www.runoob.com/cplusplus/cpp-class-access-modifiers.html

C++类构造函数与析构函数

类的构造函数

是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。析构函数的名字和类名完全相同,并且没有返回值,也不返回void
作用是:为某些成员变量设置初始值。

#include <iostream>
using namespace std;

class Line
{
   public:
      void setLength( double len );
      double getLength( void );
      Line();  // 这是构造函数

   private:
      double length;
};

// 成员函数定义,包括构造函数
Line::Line(void)
{
    cout << "Object is being created" << endl;
}

void Line::setLength( double len )
{
    length = len;
}
// 程序的主函数
int main( )
{
   Line line;

   // 设置长度
   line.setLength(6.0); 
   cout << "Length of line : " << line.getLength() <<endl;

   return 0;
}
构造函数带参数

默认的构造函数没有任何参数,但如果需要,构造函数也可以带有参数。这样在创建对象时就会给对象赋初始值。

class Line
{
   public:
      void setLength( double len );
      double getLength( void );
      Line(double len);  // 这是构造函数

   private:
      double length;
};
...
...
Line line(10.0);//main()里面,为len赋值
使用初始化列表来初始化字段
Line::Line(double len):length(len)    //为私有成员变量length赋值
{
    cout<<"Object is being created, length= "<<len<<endl;
}

上面代码等同于:

Line::Line(double len){
    length = len;
    cout<<"Object is being created, length= "<<len<<endl;
}

假如一个类C,需要多个成员变量(X Y Z)需要赋值,可以这样简写:

C::C(double X, double Y, double Z):X(a), Y(b), Z(c)
{
    .......
}

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

class Line
{
   public:
      void setLength( double len );
      double getLength( void );
      Line();   // 这是构造函数声明
      ~Line();  // 这是析构函数声明

   private:
      double length;
};

Line::~Line(void)
{
    cout << "Object is being deleted" << endl;
}

C++拷贝构造函数

拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。

  1. 通过使用另一个同类型的对象来初始化新创建的对象。
  2. 复制对象把它作为参数传递给函数。
  3. 复制对象,并从函数返回这个对象。

如果在类中没有定义拷贝构造函数,编译器会自行定义一个,如果类带有指针变量,并有动态内存分配,则他必须有一个拷贝构造函数。一般结构:

classname (const classname &obj){
    //函数主体
}

这里,obj是一个对象引用,该对象是用于初始化另一个对象的参考资料

#include <iostream>
using namespace std;

class Line
{
   public:
      Line( int len );             // 简单的构造函数
      Line( const Line &obj);      // 拷贝构造函数
      ~Line();                     // 析构函数
      int getLength( void );
   private:
      int *ptr;
};

// 成员函数定义,包括构造函数
Line::Line(int len)
{
    cout << "调用构造函数" << endl;
    // 为指针分配内存
    ptr = new int;
    *ptr = len;
}

Line::Line(const Line &obj)
{
    cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
    ptr = new int;
    *ptr = *obj.ptr; // 拷贝值
}

Line::~Line(void)
{
    cout << "释放内存" << endl;
    delete ptr;
}
int Line::getLength( void )
{
    return *ptr;
}

void display(Line obj)
{
   cout << "line 大小 : " << obj.getLength() <<endl;
}

// 程序的主函数
int main( )
{
   Line line(10);

   display(line);

   return 0;
}

例二:参考资料:c++拷贝构造函数详解

#include<iostream>
using namespace std;
class CExample
{
    private:
        int a;
    public:
        CExample(int b):a(b)    //构造函数
        {
            printf("constructor is called\n");
        }
        CExample(const CExample & c)//拷贝构造函数
        {
            a=c.a;
            printf("copy constructor is called\n");
        }
        ~CExample()    //析构函数
        {
            cout<<"destructor is called\n";
        }
        void Show()    //成员函数
        {
            cout<<a<<endl;
        }
};
void g_fun(CExample c)    //普通函数
{
    cout<<"g_func"<<endl;
}
int main()
{
    CExample A(100);
    CExample B=A;
    B.Show(); 
    g_fun(A);
    return 0;
}
/*
(1).A对象传入形参时,会先会产生一个临时变量,就叫 C 吧。
(2).然后调用拷贝构造函数把A的值给C。 整个这两个步骤有点像:CExample C(A);
(3).等g_fun()执行完后, 析构掉 C 对象。  
*/

友元函数

类的友元函数是定义在类的外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型在类中定义国,但它不是成员函数
有友元函数,也有友元类(整个类及其所有成员都是友元,可以访问)。参考资料

class Box
{
   double width;
public:
   double length;
   friend void printWidth( Box box );
   void setWidth( double wid );
};
// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box )
{
   /* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
   cout << "Width of box : " << box.width <<endl;
}

### C++内联函数

C++ 内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。(在类的内部定义的,默认都是内联函数

对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。

如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义。如果已定义的函数多于一行,编译器会忽略 inline 限定符。

在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符。

引入内联函数的目的是为了解决程序中函数调用的效率问题,这么说吧,程序在编译器编译的时候,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,而对于其他的函数,都是在运行时候才被替代。这其实就是个空间代价换时间的节省。所以内联函数一般都是1-5行的小函数。在使用内联函数时要留神:

  • 1.在内联函数内不允许使用循环语句和开关语句;
  • 2.内联函数的定义必须出现在内联函数第一次调用之前;
  • 3.类结构中所在的类说明内部定义的函数是内联函数。

有些函数即使声明为内联的也不一定会被编译器内联, 这点很重要; 比如虚函数和递归函数就不会被正常内联. 通常, 递归函数不应该声明成内联函数.(递归调用堆栈的展开并不像循环那么简单, 比如递归层数在编译时可能是未知的, 大多数编译器都不支持内联递归函数). 虚函数内联的主要原因则是想把它的函数体放在类定义内, 为了图个方便, 抑或是当作文档描述其行为, 比如精短的存取函数.

#include<iostream>
using namespace std;
inline int Max(int x, int y)
{
    return (x>y ? x:y);
}
//程序主函数
int main(void)
{
    cout << "Max(20, 10): "<<Max(20, 10)<<endl;
    return 0;
}

C++ this指针

在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。

友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。

#include <iostream>
using namespace std;

class Box
{
   public:
      // 构造函数定义
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
      }
      double Volume()
      {
         return length * breadth * height;
      }
      int compare(Box box)
      {
         return this->Volume() > box.Volume();
      }
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};

int main(void)
{
   Box Box1(3.3, 1.2, 1.5);    // Declare box1
   Box Box2(8.5, 6.0, 2.0);    // Declare box2

   if(Box1.compare(Box2))
   {
      cout << "Box2 is smaller than Box1" <<endl;
   }
   else
   {
      cout << "Box2 is equal to or larger than Box1" <<endl;
   }
   return 0;
}
/*
Constructor called.
Constructor called.
Box2 is equal to or larger than Box1
*/

C++指向类的指针

一个指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符->,就像访问指向结构的指针一样。与所有的指针一样,您必须在使用指针之前,对指针进行初始化。

Box{
    ...
}

int main(void){
    Box box;
    Box * ptr;
    ptr = &box;

    //访问box里面的成员使用->
    box->[成员函数]
    box->[成员变量]
}

二、C++继承

面向对象 中最重要的一个概念就是继承。继承允许我们依据另一个类来定义一个类,这使得维护和创建一个程序变得容易。

基类与派生类

已有的类叫做基类,新建的类称为派生类
一个类可以派生自多个类,它可以从多个基类继承数据和函数。

class derived-class: access-specifier base-class 

derived-class:派生类
access-specifier:访问权限控制符
base-class: 基类

//基类
class Shape 
{
   public:
      void setWidth(int w)
      {
         width = w;
      }
      void setHeight(int h)
      {
         height = h;
      }
   protected:
      int width;
      int height;
};
// 派生类
class Rectangle: public Shape
{
   public:
      int getArea()
      { 
         return (width * height); 
      }
};

访问控制和继承

派生类可以访问基类中所有非私有成员。因此基类成员如果不想被派生类的成员访问,则应在基类中声明为:private

访问 public protected private
同一类 yes yes yes
派生类 yes yes no
外部的类 yes no no

一个派生类继承了所有的基类方法,但是下列方法除外:

  • 基类的构造函数、析构函数和拷贝构造函数
  • 基类的重载函数
  • 基类的友元函数

继承类型

当一个类派生自基类,该基类可以被继承为:publicprotectedprivate几种类型。继承类型是通过上面的访问控制符来指定的。

通常使用publlic继承,当使用不同类型的继承时,遵循如下几个规则:

  • 公有继承 public:一个类由公有基类派生,各个成员访问控制权限保持不变。 (视情况)
  • 保护继承 protected:一个类由保护基类派生,基类的公有和保护成员就被派生成保护成员。(部分可以访问)
  • 私有继承 private:一个类由私有基类派生,基类的公有和保护成员就被派生成私有成员。 (派生的就不能访问基类的)

多继承

多继承即一个子类可以有多个基类,它继承了多个父类的特性。
语法:

class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};

代码:

#include<iostream>
using namespace std;
class Shape{
    ...
}

class PaintCost{
    ...
}

class Rectangle: public Shape,  public PaintCost{
    ...
}

环状继承

A->D B->D C->(A, B)
class D{.....};
class A: public D{....};
class B: public D{.....};
class C: public D, public B{.......};
这个继承就会使D创建两个对象,解决上述问题就要使用虚拟继承格式。

#include <iostream>

using namespace std;
//基类

class D
{
public:
    D(){cout<<"D()"<<endl;}
    ~D(){cout<<"~D()"<<endl;}
protected:
    int d;
};

class B:virtual public D
{
public:
    B(){cout<<"B()"<<endl;}
    ~B(){cout<<"~B()"<<endl;}
protected:
    int b;
};

class A:virtual public D
{
public:
    A(){cout<<"A()"<<endl;}
    ~A(){cout<<"~A()"<<endl;}
protected:
    int a;
};

class C:public B, public A
{
public:
    C(){cout<<"C()"<<endl;}
    ~C(){cout<<"~C()"<<endl;}
protected:
    int c;
};

int main()
{
    cout << "Hello World!" << endl;
    C c;   //D, B, A ,C
    cout<<sizeof(c)<<endl;
    return 0;
}

三、C++重载

C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分

重载包括:重载运算符和重载函数

C++中的函数重载

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

#include <iostream>
using namespace std;

class printData
{
   public:
      void print(int i) {
        cout << "整数为: " << i << endl;
      }

      void print(double  f) {
        cout << "浮点数为: " << f << endl;
      }

      void print(char c[]) {
        cout << "字符串为: " << c << endl;
      }
};

int main(void)
{
   printData pd;

   // 输出整数
   pd.print(5);
   // 输出浮点数
   pd.print(500.263);
   // 输出字符串
   char c[] = "Hello C++";
   pd.print(c);

   return 0;
}

C++中的运算符重载

运算符重载,是带有特殊名称的函数,函数名由关键字:operator和其后要重载的运算符符号构成的。
具体语法:Box operator+(const Box&)

参考:https://www.runoob.com/cplusplus/cpp-overloading.html

四、C++ 多态

当类之间存在层次结构,并且类之间是通过继承关系时,就会用到多态。

C++多态意味着调用成员函数时,会根据调用函数的对象类型来执行不同的函数。
代码:

class Shape{
    ....
    public:
        int area(){
            cout<<"Parent class area."<<endl;
        };
};
class Rectangle: public Shape{
    ...
    public:
        int area(){
            cout<<"Rectangle class area."<<endl;
            return ...
        }
};
class Triangle:public Shape{
    ....
    public:
        int area(){
            cout <<"Triangle class area."<<endl;
            return ..
        }
};

在分别调取Rectangle类对象和Triangle类对象的area()函数时候,得到结果是:

Parent class area.
Parent class area.

导致错误的原因是:调用函数area()被编译器设置为基类中的版本,这就是所谓的静态多态静态链接,函数调用在程序执行前就准备好了,有时候被叫做早绑定
解决办法:在Shape类下面,area()声明前面放置virtual即可解决。

...
    virtual int area(){
        ...
    }
...

输出结果变成了:

Rectangle class area
Triangle class area

虚函数

在基类中使用virtual声明的函数,在派生类中重新定义基类的虚函数,编译器就不会静态链接到该函数。

我们把想要在程序中可根据所调用对象类型来选择调用的函数的操作称为动态链接,后期绑定。

纯虚函数

想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。

    ....
    virtual int area() = 0;
    ...

五、C++数据抽象

数据抽象是指,只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节。

数据抽象是一种依赖于接口和实现分离的编程(设计)技术。资料:

六、C++ 数据封装

封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。数据封装引申出了另一个重要的 OOP 概念,即数据隐藏

数据封装是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。

七、C++ 接口(抽象类)

如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类(通常叫做ABC)。纯虚函数是通过在声明中使用 “= 0” 来指定的。

class Box{
    public:
        virtual double getVolume() = 0;
    private:
        double length;
        double breadth;
        double height;
};

设计抽象类(通常称为 ABC)的目的,是为了给其他类提供一个可以继承的适当的基类。抽象类不能被用于实例化对象,它只能作为接口使用。如果试图实例化一个抽象类的对象,会导致编译错误。可用于实例化对象的类被称为具体类

抽象类的实例:部分参考:private,子类父类继承

#include<iostream>
using namespace std;
class Shape{        //基类
    public:
        virtual int getArea() = 0;
        void setWidth(int w){
            width = w;
        }
        void setHeight(int h){
            height = h;
        }
    private:        //下面两个被派生继承了,但是不能被读取
        int width;
        int height;
};
class Rectangle: public Shape{        //派生类
    public:
        int getArea(){        //getArea()是继承他老子Shape的,有读取private的成员的能力。
            return width*height;
        }
};
class Triangle:public Shape{
  public:
        int getArea(){
            return (width*height)/2;
        }
};
int main(void){
    Rectangle Rect;
    Triangle Tri;

    Rect.setWidth(5);
    Rect.setHeight(7);
    cout<<"Total Rectangle area: "<<Rect.getArea()<<endl;

    Tri.setWidth(5);
    Tri.setHeight(7);
    cout<<"Total Triangle area: "<<Tri.getArea()<<endl;

    return 0;
}
/*
Total Rectangle area: 35
Total Triangle area: 17
*/

八、C++文件和流

之前使用了iostream标准库,下面使用一点其他文件读取标准库

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

在C++中进行文件处理,必须在C++源代码文件中包含头文件<iostream><fstream>

打开文件

可以使用ofstreamfstream对象来打开文件进行写操作,而打开文件进行写操作就必须使用ifstream对象。
打开文件需要open()函数,是前面三个对象的一个成员函数。

void open(const char*filename, ios::openmode mode);

第二个参数ios::openmode mode

模式标志 描述
ios::app 追加模式。所有写入都追加到文件末尾。
ios::ate 文件打开后定位到文件末尾。
ios::in 打开文件用于读取。
ios::out 打开文件用于写入。
ios::trunc 如果该文件已经存在,其内容将在打开文件之前被截断,即把文件 长度设为0。

将两者模式结合使用,写入模式打开文件,希望截断文件,以防止文件存在。

ofstream outfile;
outfile.open("file.txt", ios::out|ios::trunc);

类似的,打开文件用于读取(先写入后读取)。

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

关闭文件

void.close();

读取&写入实例

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

int main(int argc, char* argv[])
{
    char data[100];

    //写入模式打开文件            //输入设备
    ofstream outfile;    //outfile对象
    outfile.open("file.txt");

    cout<<"Writing to the file: "<<endl;
    cout<<"Enter your name: ";
    cin.getline(data, 100);

    outfile<<data<<endl;    //向文件写入用户输入的数据

    cout<<"Enter your age: ";
    cin>>data;
    cin.ignore();    //
    outfile<<data<<endl;    //再次向文件写入数据
    outfil.close();

    //以读模式打开文件
    ifstream infile;        //输出设备
    infile.open("file.txt");

    cout<<"Reading from the file"<<endl;
    infile >> data;

    cout << data << endl;        //在屏幕写入数据

    infile >> data;        //再次从文件读取数据,并显示它
    cout<<data <<endl;

    infile.close();

    return 0;
}

文件位置指针

istreamostream都提供了用于重新定位文件位置指针的成员函数。包含关于istrean的seekg(”seek get”)和关于ostream的seekp(“seek put”)。

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:当问题出现时,程序会抛出一个异常,通过它实现
  • catch:在您想要处理问题的地方,通过异常处理程序捕获异常。捕获异常用。
  • trytry块中的代码标识将激活的特定异常。它后面通常跟一个或多个catch块。

try/catch语句:

try{
    //保护代码
}catch( ExceptionName  e1 ){
    //catch块
}catch( ExceptionName e2 ){
    //catch块
}catch( ExceptionName eN )
{
    //catch块
}

//-------------------------------
try {
    语句组
}
catch(异常类型) {
    异常处理代码
}
...
catch(异常类型) {
    异常处理代码
}

抛出异常

throw

double division(int a, int b){
    if( b == 0 )
    {
        throw "Division by Zero condition !";
    }
    return a/b;
}

捕获异常

catch块跟在try块后面,用于捕获异常。您可以指定想要捕获的异常类型。

try{
    //保护代码
}catch( ExceptionName e){
    //处理ExceptionName异常的代码
}

实例:

#include<iostream>
using namespace std;
double division(int a, int b){
    if(b == 0)
        throw "Division by zero condition !";
    return a/b;
}

int main(int argc, char*argv[])
{
    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++异常处理(try catch throw)完全攻略

C++标准的异常

1

十、C++动态内存

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

分配空间使用new运算符,删除空间使用delete运算符。

new 和 delete 运算符

new

通用语法:new data-type;

指向double类型的指针:

double * pvalue = NULL;    //空指向的指针
if(!(pvalue = new double))    //分配    
{
    cout << "Eror: out of memory."<< endl;
    exit(1);
}

C是 int * p = (int *)malloc(sizeof(int));

在C++中仍存在malloc函数,与其相比new除了分配内存,还创建了对象。

delete

删除所占内存:

delete pvalue;

二者合用的实例:

#include<iostream>
using namespace std;
int main(void)
{
    double * pvalue = NULL;    //初始化为null的指针
    pvalue = new double;

    *pvalue = 292929.4;        //在分配的地址存储值
    cout << "Value of pvalue: " << *pvalue <<endl;

    delete pvalue;    //释放内存
    return 0;
}

数组的动态内存分配

为一个字符数组(含20个字符的字符串)分配内存。

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

删除我们创建的数组:delete [] pvalue;

一维数组

//动态分配,数组长度为m
int * array = new int [m];

//释放内存
delete [] array;

二维数组

int ** array;        //arrar为数据类型为整型的二级指针变量
//假定数组第一维长度为m, 第二维长度为 n
//动态内存分配
array = new int * [m];        //先分配x
for(int i = 0; i < m; ++i)    //后分配y
{
    array[i] = new int[n];    //一级指针指向
}
//释放
for(int i =0; i<m; ++i)
{
    delete [] array[i]        //先释放y
}
delete [] array;

三维数组

int i, j, k;
int *** array;    //array[m][n][k], 例如 array[2][3][4]
array = new int **[m];
for(i =0; i<m; ++i)
{
    array[i] = new int *[n];
    for(j = 0; j<k; ++j)
        array[m][n] = new int[k];
}

for(i = 0; i<m; ++i)
{
    for(j=0; i<n; ++j)
        delete [] array[m][n];
    delete [] array;
}
/*

     *    *
    ***  ***
 **** **** **** **** **** **** **** .... ****
*/

对象的动态内存分配

#include<iostream>
using namespace std;
class Box{
    public:
        Box(){
            cout << "use constructure function !" <<endl;
        }
        ~Box(){
            cout <<"use destory function !"<<endl;
        }
};
int main(void)
{
    Box * myBoxArray = new Box[4];
    delete [] myBoxArray;    //删除数组

    return 0;
}

十一、C++命名空间

,它可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上,命名空间就是定义了一个范围。

C++命名空间

定义命名空间

namespace namespace_name{
    //代码声明
}

如何调用命名空间中的变量或者函数呢?
namespace_name::code;
具体实例代码:

#include<iostream>
using namespace std;

namespace first_space{    //第一个命名空间
    void func(){
        cout<<"Inside first_space."<<endl;
    }
}
namespace second_space{    /第二个命名空间
    void func(){
        cout <<"Inside second_space."<<endl;
    }
}

int main(void){
    first_space::func();
    second_space::func();

    return 0;
}

using 指令

使用哪个命名空间可以使用这个指令:using namespace namespace_name
具体代码:

#include<iostream>
using namespace std;
namespace first_space{    //第一个命名空间
    void func(){
        cout<<"Inside first_space."<<endl;
    }
}
namespace second_space{    /第二个命名空间
    void func(){
        cout <<"Inside second_space."<<endl;
    }
}
using namespace first_space;
int main(void){
    func();    //自动调取第一个命名空间下的func()函数
    return 0;
}

也可以用来指定命名空间中的特定项目:using std::cout
代码:

#include<iostream>
using std::cout;
int main(void){
    cout << "std::endl is used with std !" << std::endl;
    return 0;
}

不连续的命名空间

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

namespace namespace_name{
    //代码声明
}

嵌套的命名空间

namespace namespace_name1{
        //代码声明
    namespace namespace_name2{
        //代码声明
    }
}

可以通过::运算符来访问嵌套的命名空间中的成员。

//访问namespace_name2中的成员
using namespace namespace_name1::namespace_name2;

using namespace namespace_name1;

代码示例:

#include<iostream>
using namespace std;
namespace first_space{    //第一个命名空间
    void func(){
        cout<<"Inside first_space."<<endl;
    }
    namespace second_space{    /第二个命名空间
        void func(){
            cout <<"Inside second_space."<<endl;
        }
    }
}
using namespace first_space::second_space;
int main(void)
{
    func();        //Inside second_space.

    return 0;
}

补充

关于命名空间内变量和函数及全局变量的使用和作用域

#include<iostream>
using namespace std;
namespace A{
    int a = 100;
    namespace B{
        int a = 20;
    }
}
int a = 200;
int main(int argc, char*argv[])
{
    cout << "A::a = "<<A::a <<endl;    //命名空间A下的a 100
    cout << "A::B::a"<<A::B::a<<endl;    //命名空间B下的 a 20
    cout <<"a = "<<a <<endl;    //全局变量 a 200
    cout << "::a = "<<::a<<endl; //全局变量 a 200

    int a = 30;
    cout << "a = "<<a <<endl;    //局部变量 a 30
    cout << "::a = "<<::a <<endl; //全局变量 a 200

    return 0;
}

十二、重要知识点:C++模板

模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。

函数模板

语法:

temp;ate <typename type> ret-type func-name(parameter list)
{
        //函数的主体
}
#include<iostream>
#include<string>
using namespace std;
template <typename T>
inline T const & Max(T const & a, const & b)
{
    return a < b ? b:a;
}
int main(int argc, char*argv[])
{
    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 <typename T> inline T const& Max(T const& a, const& b)的含义

  1. template <typename T>是函数模板的固定格式
  2. 使用inline,内联函数直接展开,提高代码执行效率。
  3. 为什么使用const。对于形参而言,const 是 constant 的缩写,本意是不变的,不易改变的意思,告诉编译器某值是保持不变的,同时保持代码健壮性。
  4. 使用(&)传递方式来进行参数的传递,可以不需要开辟一块程序栈,使用引用进行返回是因为参数是常引用,故使用常引用将其返回。

类模板

同函数模板意义一样。一般形式:

temlate <class type>  class class-name{
    ....
};

解释: type是占位符类型名称,可以在类被实例化的时候指定

例子:

#include<iostream>
#include<vector>    //STl标准模板里面的一个类库,向量,大小可根据需求改变
#include<cstdlib>
#include<string>
#include<stdexcept>
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(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(int srgc, char*argv[])
{
    try{
        Stack<int>  intStack;    //存储数据类型为整型的栈    1
        Stack<string>    stingStack;    //存储数据类型为string的栈    2

        //1
        intStack.push(7);
        cout << intStack.top() <<endl;

        //2
        stringStack.push("Hello");
        cout<<stringStack.top()<<endl;
        stringStack.pop();
        stringStack.pop();
       }
    catch(exception const & ex ){
        cerr<<" Exception: "<<ex.what()<<endl;
        return -1;
    }

    return 0;
}

参考:C++异常处理

十三、C++预处理器

预处理器,指一些指令指示编译器在实际编译之前需要先完成的预处理。
所有预处理指令都是以#开头,可以空格,预处理指令不是C++语句,不用以分号;结尾。

#define预处理

用于创建符号常量,该符号常量通常称为
#define macro-name replacement-text;当这段代码出现时,文件中后续出现的所有宏在编译之前都会被替换成replacement-text

#include<iostream>
using namespace std;

#define pi 3.14159

int main(int argc, char*argv[])
{
    cout<<"Value of pi: "<< pi <<endl;

    return 0;
}

参数宏

#include<iostream>
using namespace std;
#define Min(a, b)   (a<b ? a:b)

int main(void)
{
    int i, j;
    i = 100;
    j = 30;
    cout << "较小的值为: "<< Min(i, j) <<endl;

    return 0;
}

条件编译

#ifdef如果定义了,执行..

#ifdef NULL
    #define NULL 0
#endif
#ifdef DEBUG
    cerr << " Variable x = " << x <<endl;
#endif

代码注释:

#if 0
    //不进行编译的代码
#endif

具体实例:

#include<iostream>
using namespace std;
#define DEBUG

#define Min(a, b) (a<b ? a:b)

int main(void)
{
    int i, j;
    i = 100;
    j = 30;

    #ifdef DEBUG        //如果已经定义DEBUG,便执行下面语句
        cerr<<"Trace: Inside main function. "<<endl;
    #endif

    #if 0    
        cout<<"Hello"<<endl;    //这是注释部分
    #endif

    cout << "The minimum is: "<<Min(i, j)<<endl;
    #ifdef DEBUG
           cerr <<"Trace: Coming out of main function" << endl;
    #endif

    return 0;
}

#和## 运算符

#运算符会把replacemen-text令牌转换成用引号引起来的字符串。

#include<iostream>
using namespace std;
#define A(x) #x

int mainz(void)
{
    cout<< A(Hello C++) <<endl;

    return 0;
}

##运算符会把两个令牌连接起来。

#include<iostream>
using namespace std;

#define contact(a, b) a##b

int main(void)
{
    int xy = 100;
    cout << contact(x, y);
    return 0;
}

十四、C++信号处理

信号是由操作系统传给进程的中断,会提早终止一个程序。
有些信号不能被程序捕捉,但是下列信号可以在程序中捕获,并可以基于信号采用适当的动作。下列信号的定义在C++头文件<csignal>中。

信号 描述
SIGABRT 程序的异常终止,如调用abort
SIGFPE 错误的算术运算,比如除以零或导致溢出的操作
SIGILL 检测非法指令
SIGINT 程序终止(interrupt)信号
SIGSEGV 非法访问内存
SIGTERM 发送到程序的终止请求

signal()函数

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

#include<iostream>
#include<csignal>
#include<unistd.h>
using namespace std;

void signalHandler(int signum){
    cout<<"Interrupt signal("<<signum<<")received.\n"<<endl;

    exit(signum);
}

int main(int argc, argv[])
{
    //先注册信号SIGINT和信号处理程序
    signal(SIGINT, signalHandler);

    while(1){
        cout<<"Going to sleep..."<<endl;
        sleep(1);
    }

    retun 0;
}

/*
运行开始的时候是:
Going to sleep...
Going to sleep...
Going to sleep...
Going to sleep...
Going to sleep...
...
当使用Ctrl+C来中断程序,看到程序捕获信号并打印:
Interrupt signal (2) received.

这里显示2是指信号的编号是2
*/

raise()函数

使用raise()函数生成信号。
具体语法:int raise(signal sig);,这里sig是要发射信号的编码,信号包括SIGINT SIGABTR SIGFPE SIGILL SIGSEGV SIGTERM SIGHUP

#include<iostream>
#include<csignal>    
#include<unistd.h>     //unistd.h 是 C 和 C++ 程序设计语言中提供对 POSIX 操作系统 API 的访问功能的头文件的名称。该头文件由 POSIX.1 标准(可移植系统接口)提出
using namespace std;

void signalHandler(int signum )
{
    cout<<"Interrupt signal("<<signum<<")received.\n";

    exit(signum);
}

int main(void)
{
    int i=0;
    signal(SIGINT, signalHandler);    //注册信号和信号处理程序。

    while(++i)
    {
        cout<<"Going to sleep..."<<endl;
        if(i == 3){
            raise(SIGINT);
        }
        sleep(1);    //    linux里面
    }
    return 0;
}
/*
Going to sleep....
Going to sleep....
Going to sleep....
Interrupt signal (2) received.
*/

补充:
sleep()函数,windows下是毫秒,linux是秒
windows下采取的头文件是:windows.h
Linux下采用的头文件是:unistd

十五、C++ 多线程

多线程是多任务处理的一种特殊形式。多线程程序包含可以同时运行的两个或多个部分。这样的程序中的每个部分称为一个线程,每个线程定义了一个单独的执行路径。

Linux下面的操作

创建线程

#include<pthread.h>
pthread_create(thead, attr, start_routine, arg);

解释:

参数 描述
thread 指向线程标识符指针
attr 一个透明的属性对象,可以被用来设置线程属性。您可以指定线程属性对象,也可以使用默认值NULL
start_routine 线程运行函数起始地址,一旦线程被创建就会执行。
arg 运行函数的参数。它必须通过引用作为指针强制转换为void类型进行传递。没有传递参数,则使用NULL.

终止线程

#include<pthread.h>
pthread_exit(status);

在这里,pthread_exit 用于显式地退出一个线程。通常情况下,pthread_exit() 函数是在线程完成工作后无需继续存在时被调用。

如果 main() 是在它所创建的线程之前结束,并通过 pthread_exit() 退出,那么其他线程将继续执行。否则,它们将在 main() 结束时自动被终止。

#include<iostream>
#include<pthread.h>

using namespace std;
#define NUM_THREADS 5

void * Hello(void * args)
{
    cout <<"Hello !"<<endl;
    retun 0;
}

int main(void)
{
    // 定义线程的 id 变量,多个变量使用数组
    pthread_t tids[NUM_THREADS];
    for(int i=0; i<NUM_THREADS; ++i)
    {
        int ret = pthread_create(&tids[i], NULL, Hello, NULL);
        if(ret != 0)
            cout<<"pthead_create error: error_code="<< ret <<endl;
    }
    pthread_exit(NULL);

    return 0;
}
/*
结果:
Hello !
Hello !
Hello !
Hello !
Hello !
*/

创建线程并接受传入的参数

#include<iostream>
#include<cstdlib>
#include<pthread.h>

using namespace std;

#define NUM_THREADS 5

void * PrintHello(void * threadid)
{
    int tid = *( (int *)threadid );//无类型指针转换成整型指针
    cout<<"Hello Runoob! 线程 ID, "<<tid<<endl;
    pthread_exit(NULL);
}

int main(void)
{
    pthread_t threads[NUM_THREADS];
    int indexes[NUM_THREADS];//用数组保存i的值
    int rc, i;
    for(i=0; i<NUM_THREADS; ++i)
    {
        cout<<"main():创建线程,"<<i<<endl;
        rc = pthread_created(&threads[i], NULL, PrintHello, (void *)&(indexes[i]));

        if(rc){
            cout<<"Error:无法创建线程,"<<rc<<endl;
            exit(-1);
        }
    }
    pthread_exit(NULL);
    return 0;
}
/*
main() : 创建线程, 0
main() : 创建线程, 1
Hello Runoob! 线程 ID, 0
main() : 创建线程, Hello Runoob! 线程 ID, 21

main() : 创建线程, 3
Hello Runoob! 线程 ID, 2
main() : 创建线程, 4
Hello Runoob! 线程 ID, 3
Hello Runoob! 线程 ID, 4
*/

向线程传递参数

#include<iostream>
#include<cstdlib>
#include<pthread.h>
using namespace std;

#define NUM_THREADS 5

struct thread_data{
    int thread_id;
    char * message;    
};

void * PrintHello(void * threadarg){
    struct thread_data * my_data;
    my_data = (struct thread_data *)threadarg;

    cout<<"Thread ID: "<<my_data->thread_id<<endl;
    cout<<"Thread data: "<<my_data->message<<endl; 
    pthread_exit(NULL);
}

int main(void)
{
    pthread_t threads[NUM_THREADS];    //
    struct thread_data td[NUM_THREADS];    //每一个id 和message存储在数组元素中,每个元素由两部分构成
    int rc, i;

    for(i=0; i<NUM_THREADS; ++i)
    {
        cout<<"main(): creating thread, "<<i<<endl;
        td[i].thread_id = i;
        td[i].message = (char *)"This is message.";    //message是字符串类型的指针
        rc = pthread_create(&threads[i], NULL, PrintHello, (void*)&td[i]);//发送void型参数,运行线程函数

        if(rc){
            cout<<"Error: unable to create thread."<<rc<<endl;
            exit(-1);
        }
    }
    pthread_exit(NULL);
}

返回创建线程

连接和分离线程

用两个函数:

pthread_join(thread, status)
pthread_detach(threadid)

pthrea_join(threadid, status);子程序阻碍调用程序,直到指定的threadid线程终止为止。当创建一个线程时,他的某些属性会定义它是否可连接的或者可分离的。

#include<iostream>
#include<cstdlib>
#include<pthread.h>
#include<unistd.h>

using namespace std;
#define NUM_THREADS    5

void * wait(void * t)
{
    int i;
    long tid;        //long 占个4字节
    tid = (long)t;

    sleep(1);

    cout<<"Sleeping in thread"<<endl;
    cout<<"Thread with id : "<<tid<<"...exiting"<<endl;
    pthread_exit(NULL);
}

int main(void)
{
    int rc, i;
    pthread_t threads[NUM_THREADS];
    pthread_attr_t attr;
    void * status;

    //初始化并设置线程为可连接的
    pthread_attr_init(&attr);    //初始化一个线程对象的属性,pthread_attr_destory去初始化
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);    //设置线程分离(detach)状态,后面两个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和PTHREAD _CREATE_JOINABLE(非分离线程)

    for(i=0; i<NUM_THREADS; ++i)
    {
        cout <<"main(): creating thread, "<<i<<endl;    //由于开启了线程
        thread[i] = i;
        rc = pthread_create(&threads[i], NULL, wait, (void *)&i);    //返回0说明创建成功
        if(rc){
            cout<<"Error: unable to create thread. "<<rc<<endl;
            exit(-1);
        }
    }

    //删除属性,并等待其他线程
    pthread_attr_destory(&attr);
    for(i=0; i<NUM_THREADS; ++i)
    {
        rc = pthead_join(threads[i], &status);
        if(rc)
        {
            cout<<"Error: unable to join, "<<rc<<endl;
            exit(-1);
        }
        cout <<"Main: completed thread id : "<<id;
        cout <<"exiting with status: "<<status<<endl;
    }
    cout<<"Main : program exiting."<<endl;
    pthread_exit(NULL);


    return 0;
}

/*
main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Sleeping in thread 
Thread with id : 4  ...exiting 
Sleeping in thread 
Thread with id : 3  ...exiting 
Sleeping in thread 
Thread with id : 2  ...exiting 
Sleeping in thread 
Thread with id : 1  ...exiting 
Sleeping in thread 
Thread with id : 0  ...exiting 
Main: completed thread id :0  exiting with status :0
Main: completed thread id :1  exiting with status :0
Main: completed thread id :2  exiting with status :0
Main: completed thread id :3  exiting with status :0
Main: completed thread id :4  exiting with status :0
Main: program exiting.
*/

pthread_attr_setdetachstate资料参考