C++面试: C++ 中 const 和 static 关键字(定义,用途)
4、 C++ 中 const 和 static 关键字(定义,用途)
static 作用:控制变量的存储放方式和可见性。
作用一:修饰局部变量:一般情况下,对于局部变量在程序中是存放在栈区的,并且局部的生命周期在包含语句块执⾏结束时便结束了。但是如果用static 关键字修饰的话, 该变量便会存放在静态数据区,其⽣命周期会⼀直延续到整个程序执⾏结束 。但是要注意的是,虽然⽤static 对局部变量修饰之后,其生命周期以及存储空间发了变化, 但其作用域并没有改变,作⽤域还是限制在其语句块。(也就是虽然它整个程序周期都存在,但是能访问到它的作用域还是哪个局部变量的作用域)
作用二:修饰全部变量:对于一个全局变量,它既可以在本文件中被访问到,也可以在同一个⼯程中其它源⽂件被访问(添加 extern进⾏声明即可)。 ⽤ static 对全局变量修饰改变了其作用域范围,由原来的整个⼯程可⻅变成了本⽂件可⻅。
作⽤三:修饰函数:⽤ static 修饰函数,情况和修饰全局变量类似,也是改变了函数的作用域。
作⽤四:修饰类:如果 C++ 中对类中的某个函数⽤ static 修饰,则表示该函数 属于⼀个类而不是属于此类的任何特定对象 ;如果对类中的某个变量进行static 修饰,则表示该变量以及所有的对象所有,存储空间中只存在⼀个副本,可以通过类和对象去调⽤。
(补充:静态非常量数据成员,其只能在类外定义和初始化,在类内仅是声明而已。)
class Person{
public:
//在这里只是声明,初始化还是要放在类的外面
static int m_Age;
int Person::m_Age =100;//类内声明,类外初始化(初始化时要使用域操作符以表示是哪一个域的)
//访问的方式也有两种
//1.通过对象访问(所有的对象公用,所以可以通过对象访问)
//2.通过类名作用域访问 即 Person::m_Age
作⽤五:类成员/类函数声明 static
函数体内 static 变量范围为该函数体,不同于 auto 变量,该变量的内存只被分配⼀次,因此其值在下次调⽤时仍维持上次的值;
在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
在模块内的 static 函数只可被这⼀模块内的其它函数调⽤,这个函数的使⽤范围被限制在声明它的模块内;
在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
在类中的 static 成员函数属于整个类所拥有, 这个函数不接收this 指针,因⽽只能访问类 的 static 成员变量。 static 类对象必须要在类外进⾏初始化, static 修饰的变量先于对象存在,所以 static 修饰的变量要在类外初始化;
由于 static 修饰的类成员属于类,不属于对象,因此 static 类成员函数是没有 this 指针, this 指针是指向本对象的指针,正因为没有this 指针, 所以 static 类成员函数不能访问⾮ static 的类成员 ,只能访问 static修饰的类成员;
static 成员函数不能被 virtual 修饰, static 成员不属于任何对象或实例,所以加上 virtual没有任何实际意义;静态成员函数没有 this 指针,虚函数的实现是为每⼀个对象分配⼀个 vptr 指针,⽽ vptr 是通过 this 指针调⽤的,所以不能为virtual;虚函数的调⽤关系,this->vptr->ctable->virtual function。
const 关键字:含义及实现机制
在常变量(const 类型说明符 变量名)、
常引用(const 类型说明符 &引用名)、
常对象(类名 const 对象名)、
常数组(类型说明符 const 数组名[大小]): “const” 与 “类型说明符”或“类名”(其实类名是一种自定义的类型说明符) 的位置可以互换。如:
const int i;
int const i;
const class A;
class const A;
//上面两种写法的含义是一样的
//关于三种const指针
//const char*, char const*, char* const 的区别。
事实上这个概念谁都有只是三种声明方式非常相似很容易记混。
Bjarne在他的The C++ Programming Language里面给出过一个助记的方法: 把一个声明从右向左读。
char * const cp: cp is a const pointer to char
const char * p : p is a pointer to const char;
char const * p : 同上因为C++里面没有const*的运算符,所以const只能属于前面的类型。
char * const cp : 定义一个指向字符的指针常数,距离p最近的是const,说明这是一个指向字符的不能改变的指针常量
const char* p : 定义一个指向字符常量的指针 距离p最近的是char*,const 修饰char*,说明指向的是字符常量
char const* p : 等同于const char* p,因为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修饰函数返回值】
可以阻止用户修改返回值。返回值也要相应的付给一个常量或常指针。
(1)若函数的返回值是指针,且用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指向对象的值。
(2)如果函数返回值是数值(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类对象只能调用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; //写这个函数是方便常对象调用