C++语法笔记


平时记录的一些C++语法知识点。


C++

  • 成员函数指针

    成员指针并不指向一个具体的内存位置,它指向的是一个类的特定成员,而不是指向一个特定对象的特定成员,最直接的理解是将其理解为一个偏移量。

  • assert()函数

    • assert宏原型定义在#include 中。
    • 其作用是先计算表达式,如果其值为假,那么它先向stderr打印一条出错信息,然后通过调用abort来终止程序运行。
    • 缺点是:频繁的调用会极大的影响程序的性能,增加额外的开销。
  • define宏定义多行函数
    • 函数调用会带来额外的开销,它需要开辟一片栈空间,记录返回地址,将形参压栈,从函数返回还要释放堆栈。这种开销不仅会降低代码效率,而且代码量也会大大增加。函数的参数必须被声明为一种特定的类型,所以它只能在类型合适的表达式上使用,我们如果要比较两个浮点型的大小,就不得不再写一个专门针对浮点型的比较函数。
    • 宏定义在代码规模和速度方面都比函数更胜一筹;宏定义可以用于整形、长整形、单浮点型、双浮点型以及其他任何可以用“>”操作符比较值大小的类型,也就是说,宏是与类型无关的。
      • 使用#define声明多行宏函数与声明单行宏函数没有本质区别;
      • 多行声明时,回车换行前要加上字符\,即\[enter],注意字符\后要紧跟回车键,中间不能有空格或其他字符。
      • 在Linux操作系统中 \[enter]称为跳脱字符,意思是一行写不完的时候可以使用跳脱字符换行,但对于操作系统而言,它认为你并没有换行。
  • 函数模板与类模板

    • 类模板例子

       template <class T> //声明一个模板,虚拟类型名为T。注意:这里没有分号。
       class Compare //类模板名为Compare
       {
       public :
       Compare(T a,T b)
       {
          x=a;y=b;
       }
       T max( )
       {
          return (x>y)?x:y;
       }
       T min( )
       {
          return (x<y)?x:y;
       }
       private :
       T x,y;
       };
      
    • 函数模板例子

        template <class T>
        T Max(const T a, const T b)
        {
            return (a>b)?a:b;
        }
      
    • 声明类模板时需要增加一行:

       template <class 类型参数名>
      

      template意思是”模板“,是声明类模板时必须写的关键字。在template后面的尖括号的 内容为模板的参数表列,关键字class(也可以用typename)表示其后面的时类型参数。

    • 虚拟类型参数名T:

      在建立类对象时,如果将实际类型指定为int型,编译系统就会用in取代所有的T,如果指定为float型,就用float取代所有的T。这样就能实现”一类多用“。

    • 类模板外定义函数

        template <class T>
        T Compare<T>::max()
        {
          return (x > y)? x : y;
        }
      
    • 类模板的实例化

        Compare <int> cmp(4, 7);
        Compare <int> cmp(4.0, 7.0);
      
    • 模板特化

      在模板中某些参数类型需要进行特殊处理。

        template <class T>
        class Compare
        {
        public:
          bool IsEqual(const T& arg, const T& arg1);    
        };
      
        //已经不具有template的意思了,已经明确为float
        template<>
        class Compare<float>
        {
        public:
          bool IsEqual(const double& arg, const double& arg1);    
        };
      
        //已经不具有template的意思了,已经明确为double
        template<>
        class Compare<double>
        {
        public:
          bool isEqual(const double& arg, const double& arg2);   
        };
      
        template <class T>
        bool Compare<T>::IsEqual(const T& arg, const T& arg1)
        {
             cout<<"Call Compare<T>::IsEqual"<<endl;
             return (arg == arg1);
        }
      
        bool Compare<float>::IsEqual(const float& arg, const float& arg1)
        {
             cout<<"Call Compare<float>::IsEqual"<<endl;
             return (abs(arg - arg1) < 10e-3);
        }
      
        bool Compare<double>::IsEqual(const double& arg, const double& arg1)
        {
             cout<<"Call Compare<double>::IsEqual"<<endl;
             return (abs(arg - arg1) < 10e-6);
        }
      
    • 模板偏特化

      偏特化指提供一份template定义式,而其本身仍为templatized;也就是说,针对template参数更进一步的条件限制所设计出来的一个特化版本。

      模板偏特化与模板特化的区别在于:模板特化以后,实际上其本身已经不是templatized,而偏特化,仍然带有templatized

        // 一般化设计
        template <class T, class T1>
        class TestClass
        {
        public:
             TestClass()
             {
                  cout<<"T, T1"<<endl;
             }
        };
      
        // 针对普通指针的偏特化设计
        template <class T, class T1>
        class TestClass<T*, T1*>
        {
        public:
             TestClass()
             {
                  cout<<"T*, T1*"<<endl;
             }
        };
      
        // 针对const指针的偏特化设计
        template <class T, class T1>
        class TestClass<const T*, T1*>
        {
        public:
             TestClass()
             {
                  cout<<"const T*, T1*"<<endl;
             }
        };
      
  • memmove()函数:头文件<string.h>

    void *memmove(void *str1, const void *str2, size_t n) 
    

    str2复制n个字符到str1,但是在重叠内存块方面,memmove()是比memcpy()更安全的方法。

    如果目标区域和源区域有重叠的话,memmove()能够保证源串在被覆盖之前将重叠区域的字节拷贝的到目标区域中,拷贝后源区域的内容会被更改。如果目标区域与源区域没有重叠,则和memcpy()功能相同。

  • assert(0)作用

    1. 捕捉逻辑错误。可以在程序逻辑必须为真的条件上设置断言。除非发生逻辑错误,否则断言对程序无任何影响。即预防性的错误检查。
    2. 在认为不可能执行到的情况下加一句assert(0),如果运行到此,代码逻辑或条件就可能有问题
    3. 程序没写完的标识,放个assert(0)调试运行时执行到此为报错中断,好知道成员函数还没写完。
  • 显式转换(显式地将对象强制转换成另一种类型)

    • static_cast

      任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast。

      double slope = static_cast<double>(j) / i;
      

      当需要把一个较大的算数类型赋值给较小的类型时,static_cast非常有用。此时,强制类型转换告诉程序的读者和编译器:我们知道并且不在乎潜在的精度损失。

    • const_cast

      只能改变运算对象的底层const。去掉”const“性质,编译器就不再阻止我们对该对象进行写操作。

      const  char* pc;//pc是个常量
      char* p = const_cast<char*>(pc);//正确:但是通过p写值是未定义的行为
      
      const char* cp;
      char* q = static_cast<char*>(cp);//错误:static_cast不能转换掉const性质
      static_cast<string>(cp);//正确:字符串字面值转换成string类型
      const_cast<string>(cp);//错误:const_cast只改变常量属性
      
    • reinterpret_cast

      reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释。

      int* ip;
      char* pc = reinterpret_cast<char*>(ip);
      

      必须牢记pc所指的真是对象是一个int而非字符,如果把pc当场普通的字符指针使用就可能在运行时发生错误。

      string str(pc);//可能导致异常的运行的行为。
      
    • dynamic_cast

  • using

    using声明使特定的标志符可用,using编译指令使整个名称空间可用。

  • 前缀++(++i)、后缀++(i++) 的区别
    • ++i:先增加后引用,先让i加1,然后在i所在的表达式中使用i的新值。

      ++i是将i的值先+1,然后返回i的值。

      A operator ++()         //前缀++
      {
          i=i+1;
          return *this;
      }
      
    • i++:先引用后增加。先让i所在的表达式中使用i的当前值,然后让i加1。

      i++是先将i的值存到寄存器里,然后执行i+1,然后返回寄存器里的值。

      A operator ++(int)      //后缀++
      {
          A t=*this;          //先保存一份变量
          ++(*this);          //执行i+1
          return t;			//返回原值
      }
      
    • 注意:调用代码的时候,要优先使用前缀形式,除非确实需要后缀形式返回原值。

  • explicit关键字

    explicit用来声明类构造函数是显式调用的,而非隐式调用,所以只用于修饰单参构造函数

    ​ 以下是explicit关键字的注意点:

    • explicit关键字只能用于类内部的构造函数声明上。

    • explicit关键字作用于单个参数的构造函数。

    • explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换。

    //未加explicit时的隐式类型转换
    class Circle 
    { 
    public: 
        Circle(double r) : R(r) {} 
        Circle(int x, int y = 0) : X(x), Y(y) {} 
        Circle(const Circle& c) : R(c.R), X(c.X), Y(c.Y) {} 
    private: 
        double R; 
        int    X; 
        int    Y; 
    }; 
        
    int _tmain(int argc, _TCHAR* argv[]) 
    { 
        //发生隐式类型转换 
        //编译器会将它变成如下代码 
        //tmp = Circle(1.23) 
        //Circle A(tmp); 
        //tmp.~Circle(); 
        Circle A = 1.23;  
          
        //注意是int型的,调用的是Circle(int x, int y = 0) 
        //它虽然有2个参数,但后一个有默认值,任然能发生隐式转换 
        Circle B = 123; 
          
        //这个算隐式调用了拷贝构造函数 
        Circle C = A; 
        return 0; 
    } 
    
    //加了explicit关键字后,可防止以上隐式类型转换发生
    class Circle 
    { 
        public: 
        explicit Circle(double r) : R(r) {} 
        explicit Circle(int x, int y = 0) : X(x), Y(y) {} 
        explicit Circle(const Circle& c) : R(c.R), X(c.X), Y(c.Y) {} 
        private: 
        double R; 
        int    X; 
        int    Y; 
    }; 
       
    int _tmain(int argc, _TCHAR* argv[]) 
    { 
        //一下3句,都会报错 
        //Circle A = 1.23;  
        //Circle B = 123; 
        //Circle C = A; 
      
        //只能用显示的方式调用了 
        //未给拷贝构造函数加explicit之前可以这样 
        Circle A = Circle(1.23); 
        Circle B = Circle(123); 
        Circle C = A; 
      
        //给拷贝构造函数加了explicit后只能这样了 
        Circle A(1.23); 
        Circle B(123); 
        Circle C(A); 
        return 0; 
    } 
    
  • extern “c”

    实现C和C++的混合编程。

    在C++源文件中的语句前面加上extern "c",表明它按照C的编译和链接规则来进行。这样在C的代码中就可以调用C++的代码。

  • const修饰成员函数的作用

    • 声明一个成员函数的时候用const关键字是用来说明整个函数是“只读函数”,也就是说明这个函数不会修改任何数据成员

    • 为了声明一个const成员函数,const关键字放在函数括号的后面。声明和定义的时候都应该放const关键字。
    • 任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其它非const成员函数,编译器将指出错误,可以提高程序的健壮性。
    #include
    class temp
    {
    public:
        temp(int age);
        int getAge() const;
        void setNum(int num);
    private:
        int age;
    };
    temp::temp(int age)
    {
        this->age = age;
    }
    int temp::getAge() const
    {
        age += 10;//错误!getAge()为只读函数,不能修改任何数据成员。
        return age;
    }
    
  • 类static成员变量与类static成员函数

    • 类static成员变量只能在类体外进行初始化数据类型类名::静态数据成员名=初值

      如果不对静态成员变量进行显式初始化,系统缺省初始为0。

      不能在类中初始化。不能用初始化列表初始化。



本作品由 蔡家辉 创作,采用 CC BY-NC-SA 3.0 许可协议 进行许可。

Comments