C++ 快速入门笔记:进阶编程

C++入门笔记:高级编程

文件和流

  1. 打开文件

    void open (const char *filename, ios::openmode mode);
    
    • ios::app 追加模式。所有写入都追加到文件末尾
    • ios::ate 文件打开后定位到文件末尾
    • ios::in 打开文件用于读取
    • ios::out 打开文件用于写入
    • ios::trunc 如果该文件已经存在,其内容将在打开文件之前被截断,即把文件长度设为 0。
  2. 关闭文件

    void close();
    
  3. 写入文件

    • 使用流插入运算符 ( << ),向 ofstream / fstream 流中写入信息
  4. 读取文件

    • 使用流提取运算符 ( >> ),向 ifstream / fstream 流中写入信息
  5. 文件读写实例

    #include <fstream>
    #include <iostream>
    using namespace std;
    int main ()
    {
       char data[100];
       ofstream outfile;
       outfile.open("afile.dat");
       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;
       outfile.close();
       ifstream infile; 
       infile.open("afile.dat"); 
       cout << "Reading from the file" << endl; 
       infile >> data; 
       cout << data << endl;
       infile >> data; 
       cout << data << endl; 
       infile.close();
       return 0;
    }
    
  6. 文件位置指针

    • stream 和 ostream 都提供了用于重新定位文件位置指针的成员函数。

      • istream 的 seekg ("seek & get")
      • ostream 的 seekp ("seek & put")
      // 定位到 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 );
      

异常处理

  1. try / catch / throw

    • try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。
    • catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
    • throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
  2. C++ 标准的异常

    异常 描述
    std::exception 该异常是所有标准 C++ 异常的父类。
    std::bad_alloc 该异常可以通过 new 抛出。
    std::bad_cast 该异常可以通过 dynamic_cast 抛出。
    std::bad_exception 这在处理 C++ 程序中无法预期的异常时非常有用。
    std::bad_typeid 该异常可以通过 typeid 抛出。
    std::logic_error 理论上可以通过读取代码来检测到的异常。
    std::domain_error 当使用了一个无效的数学域时,会抛出该异常。
    std::invalid_argument 当使用了无效的参数时,会抛出该异常。
    std::length_error 当创建了太长的 std::string 时,会抛出该异常。
    std::out_of_range 该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator
    std::runtime_error 理论上不可以通过读取代码来检测到的异常。
    std::overflow_error 当发生数学上溢时,会抛出该异常。
    std::range_error 当尝试存储超出范围的值时,会抛出该异常。
    std::underflow_error 当发生数学下溢时,会抛出该异常。
  3. 定义新的异常

    • 通过继承或重载 exception 类来定义新的异常
      #include <iostream>
      #include <exception>
      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)
          {
              //其他的错误
          }
      }
      

动态内存

  1. C++ 程序中的内存分为两个部分

    • 栈:在函数内部声明的所有变量都将占用栈内存。
    • 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。
  2. 可以使用 new 运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。

  3. 不需要动态分配内存时,可以使用 delete 运算符,删除之前由 new 运算符分配的内存。

  4. new 和 delete 运算符

    • 使用 new 运算符来为任意的数据类型动态分配内存

      // new data-type;
      // 如果自由存储区已被用完,可能无法成功分配内存。所以建议检查 new 运算符是否返回 NULL 指针。
      double* pvalue  = NULL;
      if( !(pvalue  = new double ))
      {
          cout << "Error: out of memory." <<endl;
          exit(1);
      
      }
      
    • 使用 delete 操作符释放它所占用的内存

      #include <iostream>
      using namespace std;
      
      int main ()
      {
         double* pvalue  = NULL; // 初始化为 null 的指针
         pvalue  = new double;   // 为变量请求内存
         *pvalue = 29494.99;     // 在分配的地址存储值
         cout << "Value of pvalue : " << *pvalue << endl;
         delete pvalue;         // 释放内存
         return 0;
      }
      
  5. 数组的动态内存分配

    int ROW = 2;
    int COL = 3;
    double **pvalue  = new double* [ROW]; // 为行分配内存
    
    // 为列分配内存
    for(int i = 0; i < COL; i++) {
        pvalue[i] = new double[COL];
    }
    for(int i = 0; i < COL; i++) {
        delete[] pvalue[i];
    }
    delete [] pvalue; 
    
  6. 对象的动态内存分配

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

命名空间

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

  2. 定义命名空间

    #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 ()
    {
         
        // 调用第一个命名空间中的函数
        first_space::func();
           
        // 调用第二个命名空间中的函数
        second_space::func(); 
        
        return 0;
    

}
```

  1. using 指令

    #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 ()
    {
         
        // 调用第一个命名空间中的函数
        func();
           
        return 0;
    }
    
    #include <iostream>
    using std::cout;
    
    int main ()
    {
        cout << "std::endl is used with std!" << std::endl;
       
        return 0;
    }
    
  2. 不连续的命名空间

    • 命名空间可以定义在几个不同的部分中,因此命名空间是由几个单独定义的部分组成的。一个命名空间的各个组成部分可以分散在多个文件中。
  3. 嵌套的命名空间

    #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 ()
    {
        // 调用第二个命名空间中的函数
        func();
        return 0;
    }
    

模板

  1. 模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。

  2. 模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。每个容器都有一个单一的定义,比如 向量,我们可以定义许多不同类型的向量,比如 vector <int> 或 vector <string>。

  3. 函数模板

    template <class type> ret-type func-name(parameter list)
    {
       // 函数的主体
    } 
    
    • 在这里,type 是函数所使用的数据类型的占位符名称。这个名称可以在函数定义中使用。
    #include <iostream>
    #include <string>
    
    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;
    }
    
  4. 类模板

    template <class type> class class-name {
    
    }
    
    • 在这里,type 是占位符类型名称,可以在类被实例化的时候进行指定。您可以使用一个逗号分隔的列表来定义多个泛型数据类型。
    #include <iostream>
    #include <vector>
    #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 (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;
        } 
    }
    

预处理器

  1. #define 预处理

    • define 预处理指令用于创建符号常量。该符号常量通常称为宏。

      #define macro-name replacement-text 
      
  2. 函数宏

    • 使用 #define 来定义一个带有参数的宏:

      #define MIN(a,b) (((a)<(b)) ? a : b)
      
  3. 条件编译

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

    • ifdef & endif

      #ifndef NULL
         #define NULL 0
      #endif
      
    • DEBUG 模式

      #ifdef DEBUG
          cerr <<"Variable x = " << x << endl;
      #endif
      
    • 可以使用 #if 0 语句注释掉程序的一部分。

      #if 0
          不进行编译的代码
      #endif
      
  4. # 和 ## 运算符

    • 运算符会把 replacement-text 令牌转换为用引号引起来的字符串。

      #include <iostream>
      using namespace std;
      #define MKSTR( x ) #x
      
      int main ()
      {
          cout << MKSTR(HELLO C++) << endl;
          return 0;
      }
      
    • 运算符用于连接两个令牌。

      #include <iostream>
      using namespace std;
      #define concat(a, b) a ## b
      int main()
      {
          int xy = 100;   
          cout << concat(x, y);
          return 0;
      }
      
  5. 预定义宏

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

信号处理

  1. 信号是由操作系统传给进程的中断,会提早终止一个程序。在 UNIX、LINUX、Mac OS X 或 Windows 系统上,可以通过按 Ctrl+C 产生中断。

  2. 可捕获的信号表(定义在 <csignal> 中)

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

    • 用来捕获突发事件

      void (*signal (int sig, void (*func)(int))(int);
      
    • 接收两个参数:第一个参数是一个整数,代表信号编号;第二个参数是一个指向信号处理函数的指针。

    • 无论要在程序中捕获什么信号,都必须使用 singal 函数来注册信号,并将其与信号处理程序相关联。

      #include <iostream>
      #include <csignal>
      using namespace std;
      void signalHandler( int signum )
      {
          cout << "Interrupt signal (" << signum << ") received.\n";
          // 清理并关闭
          // 终止程序  
          exit(signum);  
      }
      
      int main ()
      {
          // 注册信号 SIGINT 和信号处理程序
          signal(SIGINT, signalHandler);  
          while(1){
              cout << "Going to sleep...." << endl;
              sleep(1);
          }
          return 0;
      }
      
  4. raise() 函数

    • 使用函数 raise() 生成信号。

      int raise (signal sig);
      
    • sig 是要发送的信号编号,这些信号包括:SIGINT SIGABRT SIGFPE SIGILL SIGSEGV SIGTERM SIGHUP

      #include <iostream>
      #include <csignal>
      using namespace std;
      void signalHandler( int signum )
      {
          cout << "Interrupt signal (" << signum << ") received.\n";
          // 清理并关闭
          // 终止程序 
          exit(signum);  
      }
      int main ()
      {
          int i = 0;
          // 注册信号 SIGINT 和信号处理程序
          signal(SIGINT, signalHandler);  
          while(++i){
              cout << "Going to sleep...." << endl;
              if( i == 3 ){
                  raise( SIGINT);
              }
              sleep(1);
          }
          return 0;
      }
      
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容