zoukankan      html  css  js  c++  java
  • const浅析

    前言

    c++中使用到const的地方有很多, 而且const 本身也针对不同的类型可能有不同的含义, 比如对指针就有顶层和底层. 本节就是探讨关于C++中const的在不同的地方不同表现或含义.

    const

    关于const :

    • const修饰的对象一旦创建一般就不能改变, 所以对于const对象必须进行初始化.

      int i = 0;
      const int j; 	// error. 必须进行初始化
      const int j = 0;
      
    • 初始化时并不关心初始化对象的是const还是非const

      int i = 0;
      const int j = i;	// i 是非const也可以
      
    • const不能改变

      const int i = 0;
      i = 1; 	// error const的对象一般不能进行修改
      
    • 引用对象的类型必须与其所引用对象的类型一致

      int i = 0;
      int &j = i;
      double &size = j; // error. size与j的类型不一致
      
      • 因为以上引用的规则, 所以const类型的引用只能被const的对象引用
      int i = 0;
      const int &size = i;
      int &j = size; // error. size的类型为const int, j的类型为 int. 两者并不匹配
      
      • 引用类型对应的例外

        int size = 0;
        const double &i = size; // size与i的类型虽然不一致, 但是因为const的原因使得等式成立
        

        原因 : 虽然i与size两者的类型并不一致, 但是初始化i时, 编译器会为size生成一个临时量(double j = size;), 然后i最终绑定在这个临时量上(const double &i = j ). i 之所以能绑定在一个临时量上, 还是因为const的对象不能被修改, 则i 无法被修改, 保障了临时量不会被改变.

        注意 i实际绑定在临时量上, 并没有绑定在size上

        int size = 0;
        const double &i = size;
        size = 1;	// i 实际值并没有改变, 它绑定的是临时量不是size
        
    • 修改const对象的值

      int i = 0;
      const int size = i;
      const int &j = i;
      const_cast<int&>(size) = 1;	// 将size的值修改为1
      i = 2;	// 因为j绑定i, i被修改则j也被修改
      

      因为const只是对修饰的对象限制其不能修改, 不能保证对象一定是常量, 所以能保证是常量的对象最好都定义成constexpr . 对constexpr不清楚的可以看一下constexpr浅析

    顶层与底层const概念

    顶层const : 指针本身是一个常量(即地址不允许改变).

    其实我们一直都有在用顶层const, 比如int i = 0;, 这就是一个顶层const, 因为 i 的地址不会改变, 只有值会被改变.

    int size = 0, i = 0;	// 其实是顶层const
    int *const p = &size; // const直接修饰指针本身, 顶层const
    p = &i;		// error. p是顶层const
    *p = 1;		// 顶层const可以直接修改值
    

    底层const : 指针所指的对象是一个常量(指针本身是可以修改的, 只是指向的值不能进行修改).

    int size = 0, i = 0;
    const int * p = &size; // const直接修饰指针指向的对象, 底层const
    ptr = &i;	// ptr可以重新指向其他地址, 因为是底层const
    *ptr = 1;	// error. 底层const不能直接修改指向的值
    

    当然我们可以将一个对象修饰为既是顶层又是底层

    int size = 0;
    const int * const p = &size;	// 既是顶层又是底层
    const int i = 0;	// 既是顶层又是底层
    

    有一点一定要注意 : 顶层const被拷贝时会忽略掉顶层const

    const int i = 0;
    int size = i; 	// 这里的顶层const被忽略掉了
    auto j = i; 	// 此时 auto 推断出 j的类型为 int , i 的顶层const被忽略了
    

    const与重载函数

    在重载函数时, const类型的参数可能会在处理顶层const与底层const的时候出现问题. 具体什么问题分析之后再来总结.

    void Ccount(int ccount) {}			// ccount为顶层const
    void Ccount(const int ccount) {}	// error. ccount也是为顶层const, 赋值时会忽略掉顶层const, 就与上面的函数一样了
    
    void Ccount_pointer(int *ccount) {}	// ccount为顶层const
    void Ccount_pointer(int *const ccount) {}	// error. ccount也是为顶层const, 赋值时会忽略掉顶层const, 就与上面的函数一样了
    

    上面可以看出来, 因为顶层const会被忽略, 所以顶层const与另外顶层const不能被区分出来.

    // error. 在函数调用的时候有二义性, 并不能区分调用哪一个函数, 在编译期间报错. 
    void const_reference(int i) {}	// i 是顶层const. 参数类型为 int
    void const_reference(int &i) {}	// i 是顶层const. 参数类型为 int
    
    // 下面都没有问题
    void const_reference(int &i) {}	// i 是顶层const. 参数类型为 int
    void const_reference(const int &i) {}	// i 是底层const. 参数类型为const int
    
    void const_pointer(int *i) {}		// 顶层const
    void const_pointer(const int *i) {}	// 底层const
    

    因为引用对象的类型必须相同, 所以int &iconst int &i有区别, 前者类型为int , 后者类型为const int, 所以后者是底层const.

    上面可以看出来, 因为底层const不会被忽略, 底层与底层有区分, 所以可以底层const可以用来重载.

    const与类的常量成员函数

    如果const放在函数名的前面其意义只是告诉编译器返回类型是const类型的常量而已, 但是如果把const放在函数名后那就又是另一种情况了, 我们这里主要分析的就种情况.

    const int const_func(int i) {return i;}	// 这里函数返回的是const类型的, 即常量
    int const_func(int i) const {return i;}	// error. const不能直接放在普通函数名的后面, 只能放在成员函数(类函数)名的后面,原因之后分析.
    

    定义一个简单的类

    class A {
        private: int nun;
        public: int const_func() const {return 0;}	// success
    };
    // 如果将函数改为
    int const_func() const { ++num; return 0;}	// error
    

    int const_func() const函数中const是告诉编译器, 类中定义的非静态变量都不能进行修改. 原因在于类的所有成员函数都会隐式的传入this 指针, 即上面的成员函数被修改为

    int const_func(const A * const this)  { ++num; return 0;}
    

    this指针本身就是顶层const, 而放在函数名后面的const是为了修饰this指针的, 但是因为this指针不能显示的被传入, 所以const只能放在函数名后.

    知道了这里const修饰的是this 指针, 所以this->i就不能被修改了, 而静态成员不是属于实例化类本身, 也就没有this指向静态变量, 所以可以在以上类型的函数中修改静态变量.

    const放在成员函数名后面的函数我们称为常量成员函数

    但是有的时候非要在以上函数中改变某个变量的值怎么办? c++中有mutable关键字, 就是允许这样的特例发生. mutable就是告诉编译器, num可以在任何函数中进行修改.

    class A {
        private: mutable int nun;
        public: void const_func() const {++num;}	// success
    };
    

    const与类

    我们在定义类的实例化时, 可能会将类实例化定义为const, 即

    class A {
        private: int nun;
        public: int const_func()  {return 0;}	
    };
    A a;
    const A ca;
    a.const_func(); // success
    ca.const_func(); // error
    

    上面出错的原因在于ca的类型为const, 所以与之对应的函数应该是常量成员函数, 所以最好在定义类函数实现时, 重载一个常量成员函数.

    const与类静态成员

    同样上面的类为例子

    class A {
        private: static int nun = 0; // error
        public: int const_func() const {++num; return 0;}	// success. 原因上面分析了
    };
    

    在类中定义的静态变量不能在类中初始化, 必须在类外进行初始化, 不然报错. 所以上面应该在类外改为int A::num = 0; .

    但是有一个例外 :

    class A {
        private: const static int nun = 0; // success
    };
    

    因为const要求必须在创建的时候就需要对其初始化, 所以上式的例子才成立.

    const与typedef

    当我们不愿意每次都定义指针的时候, 就想到用typedef来定义指针类型. 即:

    typedef char * Str;
    char *str1 = "hello";
    const char *str2 = "hello";
    const Str str3 = "hello";
    

    对其进行相同的操作

    str1[0] = 'a';
    str2[0] = 'a';	// error
    str3[0] = 'a';	// success
    
    str1++;
    str2++;	// success
    str3++;	// error
    

    以上面的执行的操作可以看出来typedef不仅仅只是一个替换, 它将const Str str3转换为了char *const str3而不是跟str2一样.

    原因是 : char *重写声明之后, 真实的数据类型变成了char 而不是char *, 反而*成了声明符的一部分了, 导致const Str的数据类型为const char, 而*修饰const char, 也就成了常量指针.

    总结

    本节汇总了部分关于const用法的注意点, 可能看起来会很晕, 也不是一次性就容易记住, 希望在看的时候最好也进行验证是最好的. 最主要记住底层const和顶层const, 怎样重载, 基本很多的问题都是衍生.

  • 相关阅读:
    .net项目的svn Global ignore pattern
    Ionic开发中常见问题和解决方案记录
    iOS开发:mac使用svn管理项目
    iOS开发:本地数据存储-NSUserDefaults
    iOS开发:插件记录
    iOS开发:告诉git不要跟踪UserInterfaceState.xcuserstate
    发布 windows 10 universal app 时微软账号验证失败
    iOS开发:UINavigationController常用操作
    iOS开发:Swift多线程NSOperation的使用
    iOS开发:Swift多线程GCD的使用
  • 原文地址:https://www.cnblogs.com/0xfffffff0/p/10113644.html
Copyright © 2011-2022 走看看