const的作用和用法

一、const的作用

  • 1、关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了
    告诉了用户这个参数的应用目的。可以避免无意中由于修改数据造成的编程错误。

  • 2、使用const使得函数能够处理const和非const实参。

  • 3、通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。

二、const的用法

常变量: const 类型说明符 变量名

常引用: const 类型说明符 &引用名

常指针: const 类型说明符* 指针名 ,类型说明符* const 指针名

函数的const形参、实参和返回值

常对象: 类名 const 对象名

常成员函数: 类名::fun(形参) const

常数组: 类型说明符 const 数组名[大小]

在常变量(const 类型说明符 变量名)、常引用(const 类型说明符 &引用名)、常对象(类名 const 对象名)、 常数组(类型说明符 const 数组名[大小]), const” 与 “类型说明符”或“类名”(其实类名是一种自定义的类型说明符) 的位置可以互换。如:

const int a=5; 与 int const a=5; 等同

类名 const 对象名 与 const 类名 对象名 等同

默认状态下,const对象仅在文件内有效。 如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字。

1.【常量】

取代了C中的宏定义,声明时必须进行初始化(!c++类中则不然)。const限制了常量的使用方式,并没有描述常量应该如何分配。如果编译器知道了某const的所有使用,它甚至可以不为该const分配空间。最简单的常见情况就是常量的值在编译时已知,而且不需要分配存储。―《C++ Program Language》
用const声明的变量虽然增加了分配空间,但是可以保证类型安全。
C标准中,const定义的常量是全局的,C++中视声明位置而定。

2.【const的引用】

可以把引用绑定到const对象上,称之为 对常量的引用 。对常量的引用不能被用作修改它所绑定的对象。

引用的类型必须与其所引用对象的类型一致。但有两个例外情况:

  • 第一种情况是在初始化常量引用时允许用任意表达式作为初始值 ,只要该表达式的结果能转换成引用的类型。
int i = 78;
const int &y1 = 95;
const int &y2 = i * 2;
const int &y3 = y2 * i;
  • 第二种情况是对const的引用(常量引用)可能会引用一个非const的对象。常量引用仅对引用可参与的操作进行了限定,对于引用的对象本身是不是一个常量未作限定。因为对象也可能是非常量,所以允许通过其他途径改变它的值。
int j = 78;
int &y1 = j;   //引用y1绑定对象j
const int &y2 = j;   //y2也绑定对象j,但是不允许通过y2修改j的值

3.【指针和常量】

使用指针时涉及到两个对象:该指针本身和被它所指的对象

  • 将一个指针的声明用const“预先固定”将使那个对象而不是使这个指针成为常量,即指向常量的指针
    和常量引用一样,指向常量的指针也没有规定其所指的对象必须是一个常量。所谓指向常量的指针仅仅要求不能通过该指针改变对象的值。
  • 要将指针本身而不是被指对象声明为常量,即常量指针,必须使用声明运算符*const。常量指针必须初始化。
int pi = 78;
int *const cp = π //常量指针
int const *pc1; //指向常量的指针
const int *pc2; //指向常量的指针(后两个声明是等同的)
const int *const pp = π   //pp是一个指向常量对象的常量指针

4.【const修饰函数的形参和实参】

将函数形参声明为const,不想让调用函数能够修改对象的值。同理,将指针参数声明为const,函数将不修改由这个参数所指的对象。

  • 函数申明和定义的参数为形参,函数调用时的参数为实参。实参是形参的初始化,当用实参初始化形参时会忽略掉顶层const。即形参的顶层const被忽略了。当形参有顶层const时,传给它常量对像和非常量对象都是可以的。
  • 函数形参使用引用,尽量使用常量引用。因为我们不能把const对象、字面值或者需要类型转换的对象传递给普通的引用形参。

5.【const修饰函数返回值】

可以阻止用户修改返回值。返回值也要相应的付给一个常量或常指针。

若函数的返回值是指针,且用const修饰,则函数返回值指向的内容是常数,不可被修改,此返回值仅能赋值给const修饰的相同类型的指针。如:

const int * f1()
{
	int * p;
	p = new int;
	*p = 1;
	return p;
}
int main()
{
	const int * p1;
	p1 = f1();
	return 0;
}

若主函数改为:

int main()
{
	const int * p1;
	p1 = f1();
	*p1 = 2;
	return 0;
}

则编译时报错:"[10] error: assignment of read-only location ‘* p1’" (编译器code::block);因为修改了p1指向对象的值。

如果函数返回值是数值(by value),因C++中,返回值会被复制到外部临时的存储单元中,故const 修饰,中没有任何价值的。例:不要把函数int fun1() 写成const int func1()。

如果返回值是对象,将函数A fun2() 改写为const A & fun2()的确能提高效率。但此要注意,要确定函数究竟是想返回一个对象的“copy”,还是仅返回对象的“别名”即可,否则程序会出错。

5.【const修饰成员函数(c++特性)】

const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数;
const对象的成员是不能修改的,而通过指针维护的对象确实可以修改的;
const成员函数不可以修改对象的数据,不管对象是否具有const性质。编译时以是否修改成员数据为依据进行检查。
  面向对象程序设计中,为了体现封装性,通常不允许直接修改类对象的数据成员。若要修改类对象,应调用公有成员函数来完成。为了保证const对象的常量性,编译器须区分不安全与安全的成员函数(即区分试图修改类对象与不修改类对象的函数)。例如,

const Screen blankScreen;
blankScreen.display(); // 对象的读操作
blankScreen.set(*); // 错误:const类对象不允许修改

在C++中,只有被声明为const的成员函数才能被一个const类对象调用。

要声明一个const类型的类成员函数,只需要在成员函数参数列表后加上关键字const,例如,

class Screen {
 public:
 char get() const;
};

在类体之外定义const成员函数时,还必须加上const关键字,例如

char Screen::get() const {
 return _screen[_cursor];
}

若将成员成员函数声明为const,则该函数不允许修改类的数据成员。例如,

class Screen {
 public:
 int ok() const {return _cursor; }
 int error(intival) const { _cursor = ival; }
};

在上面成员函数的定义中,ok()的定义是合法的,error()的定义则非法。

值得注意的是,把一个成员函数声明为const可以保证这个成员函数不修改数据成员,但是,如果据成员是指针,则const成员函数并不能保证不修改指针指向的对象,编译器不会把这种修改检测为错误。例如,

class Name {
	public:
		void setName(const string &s) const;
	private:	
		char *m_sName;
}; 
void setName(const string &s) const {
	m_sName = s.c_str(); // 错误!不能修改m_sName;
	for (int i = 0; i < s.size(); ++i) 
		m_sName[i] = s[i]; // 不好的风格,但不是错误的
}

虽然m_Name不能被修改,但m_sName是char *类型,const成员函数可以修改其所指向的字符。
const成员函数可以被具有相同参数列表的非const成员函数重载,例如,

class Screen {
	public:
	char get(int x,int y);
    char get(int x,int y) const;
 };

在这种情况下,类对象的常量性决定调用哪个函数。

const Screen cs;
Screen cc2;
char ch = cs.get(0, 0); // 调用const成员函数
ch = cs2.get(0, 0); // 调用非const成员函数

小结:
1)const成员函数可以访问非const对象的非const数据成员、const数据成员,也可以访问const对象内的所有数据成员;
2)非const成员函数可以访问非const对象的非const数据成员、const数据成员,但不可以访问const对象的任意数据成员;
3)作为一种良好的编程风格,在声明一个成员函数时,若该成员函数并不对数据成员进行修改操作,应尽可能将该成员函数声明为const 成员函数。

1.用const 修饰函数的参数

如果参数作输出用,不论它是什么数据类型,也不论它采用“指针传递”还是“引用传递”,都不能加const 修饰,否则该参数将失去输出功能。const 只能修饰输入参数:
如果输入参数采用“指针传递”,那么加const 修饰可以防止意外地改动该指针,起到保护作用。
例如StringCopy 函数:

void StringCopy(char *strDestination, const char *strSource);

其中strSource 是输入参数,strDestination 是输出参数。给strSource 加上const修饰后,如果函数体内的语句试图改动strSource 的内容,编译器将指出错误。
如果输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不要加const 修饰。
例如不要将函数void Func1(int x) 写成void Func1(const int x)。同理不要将函数void Func2(A a) 写成void Func2(const A a)。其中A 为用户自定义的数据类型。
对于非内部数据类型的参数而言,象void Func(A a) 这样声明的函数注定效率比较底。因为函数体内将产生A 类型的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间。
为了提高效率,可以将函数声明改为void Func(A &a),因为“引用传递”仅借用一下参数的别名而已,不需要产生临时对象。但是函数void Func(A &a) 存在一个缺点:
“引用传递”有可能改变参数a,这是我们不期望的。解决这个问题很容易,加const修饰即可,因此函数最终成为void Func(const A &a)。
以此类推,是否应将void Func(int x) 改写为void Func(const int &x),以便提高效率?完全没有必要,因为内部数据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当。
问题是如此的缠绵,我只好将“const &”修饰输入参数的用法总结一下。
对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const 引用传递”,目的是提高效率。例如将void Func(A a) 改为void Func(const A &a)。
对于内部数据类型的输入参数,不要将“值传递”的方式改为“const 引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x) 不应该改为void Func(const int &x)。

2. 用const 修饰函数的返回值

如果给以“指针传递”方式的函数返回值加const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。
例如函数:
const char * GetString(void);
如下语句将出现编译错误:
char *str = GetString();
正确的用法是
const char *str = GetString();
如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const 修饰没有任何价值。
例如不要把函数int GetInt(void) 写成const int GetInt(void)。
同理不要把函数A GetA(void) 写成const A GetA(void),其中A 为用户自定义的数据类型。
如果返回值不是内部数据类型,将函数A GetA(void) 改写为const A & GetA(void)的确能提高效率。但此时千万千万要小心,一定要搞清楚函数究竟是想返回一个对象的“拷贝”还是仅返回“别名”就可以了,否则程序会出错。
函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达。
参考文章:
https://blog.csdn.net/xp731574722/article/details/79546790
https://www.cnblogs.com/lvmf/p/10793170.html

你可能感兴趣的:(C++知识面经,#,C++面向过程部分,C++学习)