文章目录
1.初识const
const 是常量的意思,被其修饰的变量不可修改。如果修饰的是类、结构体、结构体的指针,其成员也不可以更改。
#include <iostream>
using namespace std;
struct Date
{
int year;
int month;
int day;
};
int main()
{
const int age = 20;
//age = 18; // 报错
const Date d1 = { 2011, 2, 5 };
//d1.year = 2015; // 报错
Date d2 = { 2013, 4, 8 };
//d1 = d2; // 报错
return 0;
}
2.const修饰指针
结论:const修饰的是其右边的内容,类型说明符号(如int)的位置不影响const。
#include <iostream>
using namespace std;
int main()
{
int age = 10;
int height = 30;
// p1不是常量,*p1是常量
const int* p1 = &age;
// p2不是常量,*p2是常量
int const* p2 = &age;
// p3是常量,*p3不是常量
int* const p3 = &age;
// p4是常量,*p4也是常量
const int* const p4 = &age;
// p5是常量,*p5也是常量
int const* const p5 = &age;
return 0;
}
3.const修饰对象指针
#include <iostream>
using namespace std;
struct Student
{
int age;
};
int main()
{
Student stu1 = { 10 };
Student stu2 = { 20 };
const Student* p1 = &stu1;
//*p1 = stu2; // 报错
//(*p1).age = 30; // 报错
//p1->age = 30; // 报错
p1 = &stu2;
Student* const p2 = &stu2;
*p2 = stu1;
(*p2).age = 30;
p2->age = 30;
//p2 = &stu1; // 报错
return 0;
}
4.const修饰引用
引用的本质就是指针,只是编译器削弱了它的功能,所以引用就是弱化了的指针。
引用可以被 const 修饰,这样就无法通过引用修改数据了,称为常引用。const
必须写在 &
符号的左边,才能算是常引用。
引用 int &p
相当于 int* const p
,常引用 const int &p
相当于 const int* const p
。
#include <iostream>
using namespace std;
int main() {
int height = 20;
int age = 10;
// p1不能修改指向,但是可以利用p1间接修改所指向的变量
int* const p1 = &age;
//p1 = &height; // 报错
*p1 = 30;
cout << age << endl; // 30
// ref1不能修改指向,但是可以通过ref1间接修改所指向的变量
int & const ref1 = age;
ref1 = 40;
cout << age << endl; // 40
// p2可以修改指向,但是不可以利用p2间接修改所指向的变量
int const* p2 = &age;
p2 = &height;
//*p2 = 30; // 报错
// ref2不能修改指向,也不可以通过ref2间接修改所指向的变量
int const &ref2 = age; // 常引用
//ref2 = 40; // 报错
return 0;
}
4.1 const引用可以指向临时数据
举例1:const引用指向常量
#include <iostream>
using namespace std;
int main() {
const int &ref = 30;
return 0;
}
举例2:const引用指向表达式
#include <iostream>
using namespace std;
int main() {
int a = 1;
int b = 2;
const int &ref = a + b;
return 0;
}
举例3:const引用指向函数返回值
#include <iostream>
using namespace std;
int func() {
return 8;
}
int main() {
const int &ref = func();
return 0;
}
4.2 const引用可以指向不同类型的数据
#include <iostream>
using namespace std;
int main() {
int age = 10;
const double &ref = age;
return 0;
}
当常引用指向了不同类型的数据时,会产生临时变量,即引用指向的并不是初始化时的那个变量。
举例1:查看常引用指向相同类型数据的汇编代码
#include <iostream>
using namespace std;
int main() {
int age = 10;
const int &ref = age;
age = 30;
cout << age << endl; // 30
cout << ref << endl; // 30
return 0;
}
举例2:查看常引用指向不同类型数据的汇编代码
#include <iostream>
using namespace std;
int main() {
int age = 10;
const long &ref = age;
age = 30;
cout << age << endl; // 30
cout << ref << endl; // 10
return 0;
}
4.3 const引用作为函数参数
const引用作为函数参数时,可以接受const和非const实参。
非const引用作为函数参数时,只能接受非const实参。
const引用跟非const引用可以构成函数重载。
const引用作为函数参数时的上述规则也适用于const指针。
#include <iostream>
using namespace std;
int sum(int &v1, int &v2) {
cout << "sum(int &v1, int &v2)" << endl;
return v1 + v2;
}
int sum(const int &v1, const int &v2) {
cout << "sum(const int &v1, const int &v2)" << endl;
return v1 + v2;
}
int main() {
// 非const实参
int a = 10;
int b = 20;
sum(a, b);
// const实参
const int c = 10;
const int d = 20;
sum(c, d);
sum(10, 20);
return 0;
}
输出结果如下:
5.const修饰成员变量
类内初始化:在 C++11 中,我们可以为类内成员变量提供一个初始值,那么在创建对象的时候,这个初始值就用来初始化该成员变量。
const成员变量必须初始化,可以在类内部初始化,即在声明的时候直接初始化赋值;也可以在构造函数的初始化列表中进行。
#include <iostream>
using namespace std;
class Car
{
public:
const int m_price = 0;
static const int m_speed = 0;
void run()
{
cout << "Car::run()" << endl;
}
};
int main()
{
Car car;
return 0;
}
非static的const成员变量还可以在初始化列表中初始化。
#include <iostream>
using namespace std;
class Car
{
public:
const int m_price;
Car() : m_price(0)
{
}
void run()
{
cout << "Car::run()" << endl;
}
};
int main()
{
Car car;
return 0;
}
6.const修饰非静态成员函数
这里只讨论非静态的const成员函数。
const成员函数:const关键字写在参数列表后面,函数的声明和实现都必须带const。
- const成员函数内部不能修改非static成员变量
- const成员函数内部只能调用const成员函数、static成员函数
- 非const成员函数可以调用const成员函数
- const成员函数和非const成员函数构成重载
- 非const对象(指针)优先调用非const成员函数
- const对象(指针)只能调用const成员函数、static成员函数
#include <iostream>
using namespace std;
class Car
{
public:
const int m_price = 0;
void run() const;
void run();
void test()
{
run();
}
};
void Car::run() const
{
cout << "run() const" << endl;
}
void Car::run()
{
cout << "run()" << endl;
}
int main()
{
Car car1;
car1.run();
const Car car2;
car2.run();
return 0;
}
输出结果如下:
const成员函数,可以被const对象调用,也可以被非const对象调用。
非const成员函数,不能被const对象调用,只能被非const对象调用。
#include <iostream>
using namespace std;
class Student
{
public:
void print()
{
cout << "print()" << endl;
}
void display() const
{
cout << "display()-const" << endl;
}
};
int main()
{
Student stu1;
stu1.print(); // 正确
stu1.display(); // 正确
const Student stu2;
stu2.print(); // 报错
stu2.display(); // 正确
return 0;
}
7.mutable
如果一个成员变量被mutable修饰,那么这个成员变量永远处于可以被修改的状态,即便是在const成员函数中也可以被修改。
class Student
{
private:
mutable int age;
public:
void display() const
{
age = 18;
cout << "display()-const" << endl;
}
};
8.const修饰函数返回值
8.1 值传递
如果函数返回值采用值传递方式,由于函数会把返回值复制到外部临时的存储单元中,所以加const修饰没有任何价值。
例如,把函数 int getInt()
写成 const int getInt()
是没有意义的。
8.2 指针传递
如果函数返回值采用指针传递方式,它的含义和const修饰普通指针的含义基本相同。
举例1:
#include <iostream>
using namespace std;
const int* fun()
{
int* p = new int;
*p = 5;
return p;
}
int main()
{
const int* pvalue = fun(); // 指针本身可变,指针内容不可变
return 0;
}
举例2:
#include <iostream>
using namespace std;
int* const fun()
{
int* p = new int;
*p = 5;
return p;
}
int main()
{
int* const pvalue = fun(); // 指针本身不可变,指针内容可变
return 0;
}
8.3 引用传递
如果函数返回值采用引用传递方式,归根究底就是使得函数调用表达式不能作为左值。
举例:int& GetAge()
返回的是一个左值,其引用的内容可以被修改;const int& GetAgeConst()
返回的是一个右值,其引用的内容不可被修改。
#include <iostream>
using namespace std;
class Student
{
public:
int& GetAge()
{
return m_age;
}
const int& GetAgeConst()
{
return m_age;
}
void ShowAge()
{
cout << "Age:" << m_age << endl;
}
private:
int m_age = 0;
};
int main()
{
Student stu;
stu.ShowAge(); // Age:0
stu.GetAge() = 5; // 会修改成员变量的值
stu.ShowAge(); // Age:5
//stu.GetAgeConst() = 8; // 编译器会报错
//stu.ShowAge();
return 0;
}
事实上,函数返回值采用引用传递的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达。