零,常量和只读变量(不可变的变量)
const int maxn = 10;
int a[maxn];
//C程序报错
//error:variably modified 'a' at file scope
//c++ 编译通过
原因:
常量 与 只读变量 (不可变的变量)的区别:
- 常量 肯定是只读的,例如数字6, 字符串“abc”等,肯定是只读的,因为程序中根本没有地方存放它的值,当然也就不能够去修改它。
- 只读变量 则是在内存中开辟一个地方来存放它的值,只不过这个值由编译器限定不允许被修改。
- C语言 规定数组定义时下标 必须是 常量,只读变量 是不可以的。
const
- C语言中,const 就是用来限定一个变量不允许被改变的修饰符,即只读变量,因为占有存储空间,所以编译器不知道编译时的值,所以就不知道该给数组定义多大的。
- C++ 中, const修饰的 可以看成是编译期的常量,但是其是占有内存空间的。
一,C语言中的const
在c语言中, const 不是常量,只能说是不可改变的变量,C的编译器,不能将const修饰的变量看成一个编译期间的常量,因为其在内存中有分别,c编译器不能知道在编译期间的值,所以不能作为数组定义的下标。
const int a=10;
int Array[b]; //编译错误
const 类名 变量名 与 类名 const 变量名 同义
1,初始化问题
在c语言中,const int a;因为a只是个变量,且只会分配存储在“只读数据段”中内存,只读数据段中存放着常量和只读变量等不可修改的量。未初始化时,修饰全局变量默认的是0,修饰的局部变量是未知值。无论是全局变量还是局部变量,生存周期都是程序运行的整个过程。
2,使用说明
1) 修饰数组时
const int a[]={1,2,3,4,5,5}
数组变量表明已经是const的指针了,这里的const表明数组的每个单元都是const int 所以必须通过初始化进行赋值。
2)修饰一般变量
与c++中用法一样。
二,C++中的const修饰符
c++中的const 修饰变量,和宏定义基本一样,但其是占用存储空间的。 const修饰的变量内存存放问题,不同的编译器会有不同的方式。一般如下两点:
首先,对于函数体内或者类中,baseline是该怎么分配就怎么分配,根据这个object的storage class来分配,跟const修饰符无关。
然后,对于函数体外,全局变量的const变量根据编译器的不同而不同,如果这个常量同时是编译期已知的,并且你没有取址或者extern声明之类的操作,则编译器可以选择将这个常量optimize out不分配,前提是这个优化不改变你的代码的行为 且你没有引发UB(比如const_cast并强行修改常量),同时,函数体外的const变量默认为文件的局部变量。
可以修饰一般变量,成员变量,成员函数,函数的返回值,函数的参数,类的对象。
-
修饰一般变量
const常量,具有不可变性。在常变量,常引用,常对象,常数组,const与“类型说明符”或“类名”的位置可以互换。
const int a=5; 与int const a=5; 等同
类名 const 对象名 与const 类名 对象名 等同
//对于const修饰指针时除外
const int* ptr 与int* const ptr,不等同
const修饰指针时的含义是不同的。当用const进行修饰时,根据const位置的不同有三种不同效果。 判断的标准是:const修饰谁,谁的内容就是不可变的。
int a = 10;
const int*p = &a; //修饰*p, p指向的内存单元内容不可变。指针p指向可变。
a = 12; //OK
*p=24; //ERROR
p++; //OK
int const *p = &a; //修饰*p, p指向的内存单元内容不可变。指针p指向可变。
a = 13; //OK
*p=25; //ERROR
p++; //OK
int* const p = &a; //修饰p, p指向的内存单元内容可变,指针p指向不可变。
*p=26; //OK
p++; //ERROR
const int* const p = &a; //p指向的内存单元内容不可变,指针p指向不可变。
*p=26; //ERROR
p++; //ERROR
这些可以总结成这样一句话:
以*为分界点,
当const在*的左边时,实际物体值不变
当const在*的右边时,指针不变
-
修饰成员变量
被const修饰的变量只能通过参数列表初始化或者声明的时候初始化。且不能被修改。
class A{
public:
A():m_bflag(false){} //const修饰的成员变量参数列表初始化
private:
const bool m_bflag;
const int m_iStatus = 0; //const修饰的成员变量类内声明时初始化
};
-
修饰成员函数
const 修饰成员函数时,在函数的声明和定义时都必须包括const修饰符。
函数申明后,函数体之前:承诺,函数不会修改数据成员,同时只能调用const成员函数。static函数是不允许加入修饰符的
class A{
public:
bool getFlag() const {
int idx = getIndex() //编译通过
return m_bflag;
}
int getIndex()const{
test(); //编译报警,不能调用非const成员函数
return 1;
}
void test(){
return;
}
static void test1() const {} //编译告警
private:
bool m_bflag;
};
-
修饰函数参数
防止传入的参数代表的内容在函数体内被改变,但仅对指针和引用有意义。因为如果是按值传递,传给参数的仅仅是实参的副本,即使在函数体内改变了形参,实参也不会得到影响。
-
修饰函数返回值
修饰函数返回值时,如果返回类型时一般数值时,一般没有意义,如果返回值是指针或者引用时,保护指针指向的内容或引用的内容不被修改,也常用于运算符重载。归根究底就是使得函数调用表达式不能作为左值。
class A{
public:
const int getIndex(){return 1;}
const int& getNumA(){return 2;}
int& getNumB() {return 3;}
const char * getName(){return m_name.c_str();}
private:
string m_name;
};
int idx = getIndex(); //可以赋值给非const数值变量
int num = getNumA() = 4; //error const修饰的引用返回值不可以作为左值
int num = getNumB() = 5; //正确 非const修饰的引用返回值不可以作为左值
char* name1 = getName();
//error const修饰的指针只能被const修饰的同类型指针接收,否则需要强转const_cast<>()
char* name2 = const_cast<char*>(getName());
const char* name2 = getName();
-
修饰类的对象
const 修饰类的对象,只能调用const 修饰的函数,不能调用非const函数
class A{
public:
int getIndex() const{
return m_iIdex;
}
void setIndex(int idex){
m_iIdex = idex;
}
public:
int m_iIdex;
mutable int m_num;
};
const A a;
a.setIndex(1); //error,const修饰的对象只能调用const函数
a.getIndex(); //编译通过,const修饰的对象只能调用const函数
a.m_num = 2; //编译通过,mutable,说明符可以修改
a.m_iIdex = 1; //error,const修饰的对象的成员为const
-
const_cast
在c++中,提供了const_cast<new_type>(expression)强转运算符用来移植变量的const或volatile的限定,后者涉及多线程设计,不做讨论,对于const变量,不能修改它的值,这是限定符的最直接表现,const_cast并不能改变其本身的const常量属性。
const int constant = 20;
const int *const_p = &constant;
int *modifier = const_cast<int*>(const_p);
//int *modifier = const_cast<int*>(&constant);
*modifier = 7;
cout << "const_p: "<< *const_p <<endl;
cout << "modifier: "<< *modifier <<endl;
/**
constant: 21
const_p: 7
modifier: 7
**/
//constant还是保留了它原来的值。可是它们的确指向了同一个地址呀:
cout << "constant: "<< &constant <<endl;
cout << "const_p: "<< const_p <<endl;
cout << "modifier: "<< modifier <<endl;
/**
constant: 0x7fff5fbff72c
const_p: 0x7fff5fbff72c
modifier: 0x7fff5fbff72c
**/
上述说明,c++中的const是真的constant。对于修改const变量,属于:未定义行为(不可以预测的)。 虽然指向相同的地址,内存里的值也改变了,但是编译器并不从内存地址里读取数据,而是直接用用初始值替换了,类似于#define。原因是因为c++的常量折叠:指const变量(即常量)值放在编译器的符号表中,在预编译时已经替换,不会去访问内存里的值。
如下提供了可修改const修饰的变量的方法:
方法一:前提本体必须是非const变量
int num = 10;
const int* const_p = #
int *ptr = const_cast<int*>(const*p);
*ptr = 20;
//num = 20;
string str = "1234";
char *p=const_cast<char*>(str.c_str());
strcpy(p, "abcd");
//str=abcd
方法二:
const volatile int a = 10;
a=20;
三,C/C++中的const修饰符的不同
1,C标准中,const定义的常量是全局的,生命周期随整个程序。 C++中视声明位置而定。
2,const 定义的是只读变量,就相当于是定义一个常量。但是只读变量也是变量,所以 const 定义的变量仍然不能作为数组的长度。但是需要注意的是,在 c++中可以!C++ 扩展了 const 的含义,在 C++ 中用 const 定义的变量也可作为数组的长度。
const int size = 10;
int array[size]; //c++中编译通过,c语言中编译报价
3,在C中,const int a;是可以的,并且会分配内存。在C++中,必须初始化,C++为了起到和c语言一样的效果,需要将const修饰为extern,在c++ 中const 对象默认为文件的局部变量。与其他变量不同,除非特别说明,在全局作用域声明的 const 变量是定义该对象的文件的局部变量。此变量只存在于那个文件中,不能被其他文件访问。通过指定 const 变更为 extern,就可以在整个程序中访问 const 对象。
4,c语言中没有const修饰函数的语义。c++可以修饰函数,但该函数必须时成员函数。
void func() const {} //编译警告
class A{
public:
void fun() const;
}
void A::fun() const{} //编译通过