C++——const详解

文章目录

  • 顶层const vs 底层const
  • const修饰变量
    • const常量的作用
    • const和引用
    • const和指针
  • const修饰函数
  • const修饰类成员
  • const代码举例
  • constexpr

顶层const vs 底层const

顶层const
顶层const可以表示任意的对象是常量(指针、引用、int、double都可以)
比如int* const p=a:本质是一个常量型的指针,指针指向的地址不能改变,该地址存储的数据可以改变。

底层const
不是所有的对象都是底层const,只有指针和引用等复合类型可以是
比如const int* p=a:指针所指的对象是常量,即a是一个常量,p指向a的地址空间,a的值不能变,p的指向可以改变。

比较特殊的是:指针既可以是顶层const也可以是底层const,和其他类型有明显区别;

举例:
C++——const详解_第1张图片
当执行对象的拷贝操作时,常量是顶层const还是底层const区别明显。

其中,顶层const不受什么影响
你自己不变是你自己的事,把你的值拷给我也不会改变你自己,所以不受影响。

但是底层const的限制却不能忽视。
当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换才行。一般来说,非常量可以转换成常量,反之则不行

代码举例:
C++——const详解_第2张图片
上面代码分析:
p3既是顶层const也是底层const,拷贝p3时可以不在乎它是一个顶层const,但是必须清楚它指向的对象得是一个常量。
因此,不能用p3去初始化p,因为p指向的是一个普通的(非常量)整数,p可以修改p3所指空间的数据,为防止p3修改导致错误所以禁止这种行为。
另一方面,p3的值可以赋给p2,是因为这两个指针都是底层const,p3可以相信p2不会修改数据,所以可以放心的把自己指向的地址分享给p2

const修饰变量

const常量的作用

1、将变量定义为常量,防止意外的修改,增强程序的健壮性
宏定义也可以定义成常量,二者有什么区别呢?

  • const常量会进行类型安全检查,对define只进行字符替换,没有类型安全检查,在字符替换时可能会产生意料不到的错误。

  • const常量更节省空间,避免不必要的内存分配
    const常量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define宏定义的常量在内存中有若干个拷贝

2、和static一样,修饰全局变量使其可见范围为当前文件
普通全局变量的作用域是当前文件,但是在其他文件中也是可见的,使用extern声明后就可以使用

const全局变量在其他文件中是不可见的,这和添加了static关键字的效果类似

const和引用

可以把引用绑定到const对象上,就像绑定到其他对象上一样,我们称之为对常量的引用(reference to const)。与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象
C++——const详解_第3张图片
因为不允许直接为ci赋值,当然也就不能通过引用去改变ci。因此,对r2的初始化是错误的。假设该初始化合法,则可以通过r2来改变它引用对象的值,这显然是不正确的。

初始化和对const的引用
我们知道,引用的类型必须与其所引用对象的类型一致,但是有两个例外:
在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。
尤其,允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式
C++——const详解_第4张图片

对const的引用可能引用一个并非const的对象
必须认识到,常量引用仅对引用可参与的操作做出了限定,对于引用的对象本身是不是一个常量未作限定。因为对象也可能是个非常量,所以允许通过其他途径改变它的值:
C++——const详解_第5张图片
r2绑定(非常量)整数i是合法的行为。然而,不允许通过r2修改i的值。尽管如此,i的值仍然允许通过其他途径修改,既可以直接给i赋值,也可以通过像r1一样绑定到i的其他引用来修改。

const和指针

与引用一样,也可以令指针指向常量或非常量。类似于常量引用,指向常量的指针(pointer to const)不能用于改变其所指对象的值。
要想存放常量对象的地址,只能使用指向常量的指针
C++——const详解_第6张图片
指针的类型必须与其所指对象的类型一致,但也不绝对,因为允许令一个指向常量的指针指向一个非常量对象:
在这里插入图片描述
和常量引用一样,指向常量的指针也没有规定其所指的对象必须是一个常量。所谓指向常量的指针仅仅要求不能通过该指针改变对象的值,而没有规定那个对象的值不能通过其他途径改变。

试试这样想吧:所谓指向常量的指针或引用,不过是指针或引用“自以为是”罢了,它们觉得自己指向了常量,所以自觉地不去改变所指对象的值

const修饰函数

1、const修饰函数参数,表示参数不可变,若参数为引用,可以增加效率(引用传递而不用值拷贝)

2、const 修饰函数返回值,避免返回值被修改

3、const修饰成员函数(不能修饰全局函数,因为全局函数没有this指针)

  • 该函数不能修改成员变量
  • 不能调用非const成员函数,因为任何非const成员函数会有修改成员变量的企图

默认的this指针是顶层const,形如 A* const this,是指向类类型非常量版本的常量指针,

因为this是隐式的,所以它需要遵循初始化规则,意味着(在默认情况下)我们不能把this绑定到一个常量对象上,这一情况也就使得我们不能在一个常量对象上调用普通的成员函数。

如果我们想让this绑定到常量对象上怎么做呢?
由于this是隐式的并且不会出现在参数列表中,所以无法显示的将this声明成指向常量的指针。
C++的做法是允许把const关键字放在成员函数的参数列表之后,表示this是一个指向常量的指针。
像这样使用const的成员函数被称作常量成员函数常量对象,以及常量对象的引用或指针都只能调用常量成员函数

现在的this也成了底层const,所以属性值不能改变。
形如:const A* const this

修饰函数参数
 int get_data(const int a) 
修饰返回值
const int get_data(/*A*  const this*/)
    {
        return this->data;
    }
修饰成员函数
int get_data(/*const A*  const this*/)  const
    {
        return this->data;
 //因为this是指向常量的指针,所以常量成员函数不能改变调用它的对象的内容。即不能修改data
    }

const修饰类成员

1、const修饰类的成员变量
表示成员变量不能被修改,同时只能在初始化列表中赋值

2、const修饰类的成员函数
见上面修饰函数

3、const修饰类对象
对象的任何成员都不能被修改
const类对象只能调用const成员函数

类中的所有函数都可以声明为const函数吗。哪些函数不能?
1、构造函数不能
因为const修饰的成员函数不能修改成员变量。但是构造函数恰恰需要修改类的成员变量
2、static静态成员函数不行
static静态成员是属于类的,而不属于某个具体的对象,所有的对象共用static成员。this指针是某个具体对象的地址,因此static成员函数没有this指针。而函数中的const其实就是用来修饰this指针的,表示this指向的内容不可变,static静态成员却没有this指针,所以const不能用来修饰static成员函数

const代码举例

const修饰的变量、函数、对象 分别成为:常变量、常函数、常对象

常函数只能访问常变量,常对象只能访问常变量和常函数

class Student{
public:
    Student(char *name, int age, float score);
public:
    void show();
    //三个常函数
    char *getname() const;
    int getage() const;
    float getscore() const;
private://三个常变量
    char *m_name;
    int m_age;
    float m_score;
};
//常变量的赋值方式:参数列表
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }

//常函数的作用就是获取常变量的值,但是又不能修改它们的值,这种措施主要还是为了保护数据而设置的
char * Student::getname() const{
    return m_name;
}
int Student::getage() const{
    return m_age;
}
float Student::getscore() const{
    return m_score;
}

//stu、pstu 分别是常对象以及常对象指针,它们都只能调用 const 成员函数。
int main(){
    const Student stu("小明", 15, 90.6);
    //stu.show();  //error
    cout<<stu.getname()<<"的年龄是"<<stu.getage()<<",成绩是"<<stu.getscore()<<endl;
    const Student *pstu = new Student("李磊", 16, 80.5);
    //pstu -> show();  //error
    cout<<pstu->getname()<<"的年龄是"<<pstu->getage()<<",成绩是"<<pstu->getscore()<<endl;
    return 0;
}

constexpr

constexpr:constant expression,常量表达式

实际开发中,我们经常会用到常量表达式。以定义数组为例,数组的长度就必须是一个常量表达式:

// 1)
int url[10];//正确
// 2)
int url[6 + 4];//正确
// 3)
int length = 6;
int url[length];//错误,length是变量

下面的代码是正确的:

constexpr int num = 1 + 2 + 3;
int url[num] = {1,2,3,4,5,6};

为什么需要constexpr ?——极致的性能追求
对于用 C++ 编写的程序,性能往往是永恒的追求。

  • 非常量表达式只能在程序运行阶段计算出结果;
  • 而常量表达式的计算往往发生在程序的编译阶段,这可以极大提高程序的执行效率,因为表达式只需要在编译阶段计算一次,而不再需要每次运行时都计算一次。

所以,constexpr 使常量表达式获得在程序编译阶段计算出结果的能力,而不必等到程序的运行阶段

你可能感兴趣的:(编程语言,c++)