char greeting[] = "Hello";
char *p greeting; // 指针、数据都可变
const char * p = greeting; // 指针可变,数据是常量
char const * p = greeting; // 指针可变,数据是常量
char * const p = greeting; // 数据可变,指针是常量
const char * const p = greeting; // 指针、数据不可变
C的const表示修饰只读变量,但是我们可以恶意强制操作const对象。
// C
const int num = 100;
(int *)&num = 4; //可以间接修改
c++的const修饰的变量有内存实体,但是只能是右值;
C++的const变量一旦初始化,就会强制告诉编译器对这块内存的修改无效,不可能通过它去操作这块内存的值。
// c++
const int i = 0; //是一个右值,有内存实体
i = 11; //错误:向只读变量‘i’赋值
const int a{1.1}; //错误:从“double”转换到“int”需要收缩转换,请初始化成相同类型的常量
C++必须初始化const修饰的变量,除非是extern修饰的变量,C中如果不初始化const常量会警告,CPP中会报错
// C++
const int i ; // 错误:未初始化的常量‘i’ [-fpermissive]
extern const int a; //常量 变量 "a" 需要初始值设定项[严重性 “a” : 如果不是外部的,则必须初始化常量对象
const变量的初始值值可以是任意复杂的表达式
C与CPP的const之间最大的区别是如果CPP中的const初始化为寄存器常量,就会像define一样优化,如果是初始化为内存变量,编译器就不会去优化
#include
#include
#include
void main()
{
const int num{10};
*(int *)&num = 3;
std::cout << num << std::endl; //10
std::cout << *(&num) << std::endl;//10
int a{ 10 };
const int num1{ a };
*(int *)&num1 = 3;
std::cout << num1 << std::endl; //3
std::cout << *(&num1) << std::endl;//3
std::cin.get();
}
原因:如果是寄存器常量初始化const变量,寄存器直接优化,使用10替换;如果是用内存变量初始化const变量,寄存器不敢优化,必须老老实实读内存。
总结
const与别名:
关于const pstring *ps
STL迭代器是以指针为根据塑造处理的,所以迭代器的作用就像个T*指针。
#include
int main(int argc, char* argv[])
{
std::vector vec = {1, 2};
const std::vector::iterator iter = vec.begin();
*iter = 10;
// ++iter; // error: iter 是const
std::vector::const_iterator cIter = vec.begin();
++cIter; // ok
// *cIter = 10; // error:*citer是const
return 0;
}
C风格的强制转换
为什么下面的结果会是那样,没有想明白
const int num = 5; /*是一个右值,有内存实体*/
const int *pnum0 = # //内存初始化,因此不优化
int *pnum1 = (int *)# //C风格强制转换
*pnum1 = 55;
cout << pnum0 << "\t"<< pnum1 << "\t"<< &(*pnum1) << endl // 0x7ffe487a6b7c 0x7ffe487a6b7c 0x7ffe487a6b7c ---> pnum0 = pnum1 = &(*pnum1),他们指向同一块内存
<< *pnum0 << "\t"<< *pnum1 << "\t"<< num << endl ; //55 55 5
const int num = 5;
int *pnum2 = const_cast(&num); //强制去掉const属性
const int *pnum0 = #
*pnum2 = 55;
cout << pnum0 << "\t" << pnum2 << "\t" << &(*pnum2) << "\t" << &num << endl
<< *pnum2 << "\t" << num << endl;
//pnum0 = pnum1 = &(*pnum1),他们指向同一块内存
//*pnum1 = 55,num = 5
不可能通过强制类型转换修改const的值
为什么要这样设计:
#include
using namespace std;
/*去银行存钱,你传入的是变量,但是存钱程序必须传入让你的钱变成常属性,以防止被人恶意修改
可以给一个可读写内存临时限制为只读内存,可以将一个只读内存限制为只读内存const int & c
*/
int Sele(const int & c)
{
//c -= 100; // ERROR
cout << &c << endl;
return c;
}
//
int Sele1(const int & c)
{
int z = const_cast(c); //强制去掉const属性.推测:重新开辟一段内存,将c的数据复制到新内存,新内存没有const属性,可以修改。但是原来的内存仍是const
z += 1;
cout << &z << endl;
return z;
}
int main()
{
//强引用
const int num1 = 100;
int num2 = 100;
cout << "const int \t"<< &num1 << "\t" << Sele1(num1) << "\t" << num1 << endl;
cout << " int \t"<< &num2 << "\t" << Sele1(num2) << "\t" <
可以把引用绑定到const对象上,这个叫做对常量的引用
指向常量的引用仅仅要求不能通过该引用改变对象的值,但是没有规定那个对象的值不能通过其他途径改变
指向常量的指针仅仅要求不能通过该指针改变对象的值,但是没有规定那个对象的值不能通过其他途径改变
指针是对象而引用不是,因此允许把指针本身定义为常量。常量指针必须初始化,而且一旦初始化完成,则它就不能改变它的指向
阅读变量时要从右往左读,比如 int *const curErr:
const int *p0:
一个准则:非常量可以转换为常量,反之不行
#include
#include
#include
void main()
{
const int a{ 1 }; //会优化
const int b{ 4 };
int c{ 6 };
const int *p{ &a }; //p是变量,指向一个常量
//*p = 12; //error:*p是一个常量
p = &b; //p是一个变量,可以改变指向
p = &c;
c = 11; //c仍然是一个整形变量
std::cout << c << std::endl; //11
// *p = 12; //error:*p是一个常量
std::cin.get();
}
#include
#include
#include
void main12()
{
const int a{ 1 }; //会优化
const int b{ 4 };
int c{ 6 };
int d{ 0 };
//int * const p{ &a }; //p是指针常量,不可以改变指向,而且是指向一个int类型的变量
int * const p{ &c };
//p = &d;
*p = 22; //改变c
std::cout << c << std::endl; //22
c = 11;
std::cout << c << std::endl; //11
std::cin.get();
}
在一个函数声明式内,const可以和函数返回值、各参数、函数自身(如果是成员函数)产生关联
#include
#include
#include
class TextBlock{
public:
TextBlock(std::string text){
text = text;
}
const char * operator[](std::size_t position) const { // operator[] for const object
printf("const\n");
return reinterpret_cast(text[position]);
}
char& operator[](std::size_t position){ // operator[] for no-const object
printf("noconst\n");
return text[position];
}
private:
std::string text;
};
int main(int argc, char* argv[])
{
TextBlock tb("Hello"); // operator[] for no-const object
tb[0] = 'x'; // ok
const TextBlock ctb("Hello"); // // operator[] for const object
// ctb[0] = 'x'; // error
return 0;
}
bitwise constness主张:
#include
using namespace std;
class A{
private:
int i;
public:
void set(int n){ //set函数需要设置i的值,所以不能声明为const
i = n;
}
int get() const{ //get函数返回i的值,不需要对i进行修改,则可以用const修饰。防止在函数体内对i进行修改。
return i;
}
};
如果我们就是想要在const成员函数中修改普通成员变量怎么办?----- 方法是使用C++的另一个关键字mutable释放掉非静态成员变量的bitwise constness约束。
class CTextBlock{
public:
std::size_t length() const ;
private:
char *pText;
mutable std::size_t textLength;
mutable bool lengthIsValid;
};
std::size_t CTextBlock::length() const {
if (!lengthIsValid){
textLength = std::strlen(pText);
lengthIsValid = true;
}
return textLength;
}
这叫做logical constness。logical constness主张:一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不出的情况下才可以这样。
mutale是个解决方法。但它不能解决所有的const相关难题。
举个例子:假设TextBlock内的operator[]不但只是返回一个引用指向某字符,也执行边界检查、志记访问信息、数据完整性检验。把这些同时放进const和non-const operator[]中:
class CTextBlock{
public:
const char & operator[](std::size_t position) const {
// ... 执行边界检查、志记访问信息、数据完整性检验
return text[position];
}
char & operator[](std::size_t position) {
// ... 执行边界检查、志记访问信息、数据完整性检验
return text[position];
}
private:
std::string text;
};
这样的写法会有很多问题:代码重复、编译时间、维护等。 你可能会将边界访问...等代码转移到另一个(private)成员函数并让两个版本的operator[]调用它,但还是重复了一些代码,比如函数调用,两次return语句等。
你真正应该做的是实现operator[]的功能一次并使用它两次。也就是说,你必须令其中一个调用另一个,即令non-const operator[]调用其const函数
class CTextBlock{
public:
const char & operator[](std::size_t position) const {
return text[position];
}
char & operator[](std::size_t position) {
return const_cast( // 将op[]返回值的const移除
static_cast(*this) // 为*this加上const
[position] // 调用const op[]
);
}
private:
std::string text;
};
上面代码有两个转型操作:
添加const的那一次转型强迫进行了一次安全转型(将non-const对象转为const对象),所以需要用static_cast。第二次转型需要移除const,所以需要用到const_cast。
注意:
如果参数作为输出用,不论它是什么数据类型,也不论它采用“指针传递”还是“引用传递”,都不能加const 修饰,否则该参数将失去输出功能。const 只能修饰输入参数,这又分为以下几种情况。
(1) const 用于修饰“指针传递”的参数,以防意外改动指针所指数据
例如某个 StringCopy 函数:
void StringCopy(char *strDestination, const char *strSource);
其中 strSource 是输入参数,strDestination 是输出参数。给 strSource 加上 const 修饰后,如果函数体内的语句试图改动 strSource 的内容,编译器将指出错误。
当然也有方法绕过这个限制,例如可以在函数体内重命名一个指针 char * strSource2 = strSource ; 即可改动 strSource 所指的数值。
(2)const 用于修饰“指针传递”的参数,以防意外改动指针本身
考虑如下代码:
void swap ( int * const p1 , int * const p2 )
该定义将限制在函数 swap 内修改指针 p1 和 p2 的指向。
(3)值传递的效率问题
如果输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不要加const 修饰。
例如不要将函数 void Func1 (int x) 写成 void Func1(const int x)。同理不要将函数 void Func2(MyClass a) 写成void Func2(const MyClass a)。其中 MyClass 为用户自定义的数据类型。
然而,对于非内部数据类型的参数而言,类似 void Func(MyClass a) 这样声明的函数效率会比较底,这是因为函数体内将产生MyClass类型的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间。
为了提高效率,可以将函数声明改为void Func (MyClass &a),因为“引用传递”仅借用一下参数的别名而已,不需要产生临时对象。
但是函数void Func(MyClass & a) 存在一个缺点:“引用传递”有可能改变参数a,这是我们不期望的。
解决这个问题很容易,加const修饰即可,因此函数最终成为void Func(const MyClass &a)。
以此类推,是否应将void Func(int x) 改写为void Func(const int &x),以便提高效率?
答案是完全没有必要,因为内部数据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当。所以对于内部数据类型的输入参数,不要将“值传递”的方式改为“const 引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。
也是用const来修饰返回的指针或引用,保护指针指向的内容或引用的内容不被修改,也常用于运算符重载。归根究底就是 使得函数调用表达式不能作为左值。
#include
using namespace std;
class A {
private:
int i;
public:
A(){i=0;}
int & get(){
return i;
}
};
void main(){
A a;
cout<
(1)const 修饰函数返回值(返回指针)
如果给以“指针传递”方式的函数返回值加 const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。
例如函数
const char * GetString(void);
如下语句将出现编译错误:
char *str = GetString();
正确的用法是
const char *str = GetString();
(2) const 用于修饰“返回引用”函数的返回值
如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const 修饰没有任何价值。例如把函数int GetInt(void) 写成const int GetInt(void)是没有意义的。
如果返回值不是内部数据类型,将函数MyClass GetObj(void) 改写为const Myclass & GetObj(void)的确能提高效率。但此时千万千万要小心,一定要搞清楚函数究竟是想返回一个对象的“拷贝”还是仅返回“别名”就可以了,否则程序会出错。
这里对函数返回值使用 const 的目的在于限制不能将函数调用表达式作为左值使用。例如有如下函数:
int & min ( int &i, int &j);
可以对函数调用进行赋值,因为它返回的是左值: min ( a , b )=4;
但是,如果对函数的返回值限定为 const 的,即丁奕:const int & min ( int & i, int &j );
那么,就不能对 min ( a, b ) 调用进行赋值了。
事实上,函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达。(还可以用于运算符重载)
例如:
class A
{
A & operate = (const A & other); // 赋值函数
} ;
A a, b, c; // a, b, c 为A 的对象
a = b = c; // 正常的链式赋值
(a = b) = c; // 不正常的链式赋值,但合法
如果将赋值函数的返回值加const 修饰,那么该返回值的内容不允许被改动。上例中,语句 a = b = c 仍然正确,但是语句 (a = b) = c 则是非法的。
常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。比如:字面值
一个对象(或者表达式)是不是常量表达式由它的数据类型和初始值共同决定:
我们基本上不可能分配出一个初始值是不是常量表达式,因此C++11中引入了constexpr类型以让编译器来验证变量是不是一个常量表达式。
声明为constexpr的变量一定是一个常量,而且必须由常量表达式初始化:
常量表达式的值需要在编译时就得到计算,因此对声明constexpr时用到的类型必须有所限制。因为这些类型一般比较简单,值也容易得到,就将它们称为 字面量
const默认只在本文件内部有效,它的作用域是文件作用域,除非使用extern扩展它的作用域
(1)const修饰基本数据类型:基本数据类型,修饰符 const 可以⽤在类型说明符前,也可以⽤在类型说明符后, 其结果是⼀样的。在使⽤这些常ᰁ的时候,只要不改变这些常量的值即可。
(2)const修饰指针变量和引⽤变量
阅读变量时要从右往左读,比如 int *const curErr:
const int *p0:
(3)const 修饰函数参数:
防止传入的参数代表的内容在函数体内被改变,但仅对指针和引用有意义。因为如果是按值传递,传给参数的仅仅是实参的副本,即使在函数体内改变了形参,实参也不会得到影响。如:
void fun(const int i){
i = 10;
}
在函数体内是不能改变i的值的,但是没有任何实际意义。
const修饰的函数参数是指针时,代表 在函数体内不能修改该指针所指的内容,起到保护作用,在字符串复制的函数中保证不修改源字符串的情况下,实现字符串的复制。
void fun(const char * src, char * des){ //保护源字符串不被修改,若修改src则编译出错。
strcpy(des,src);
}
void main(){
char a[10]="china";
char b[20];
fun(a,b);
cout<
const修饰引用时:如果函数参数为用户自定义的类对象如:
void h(A a){
…………
…………
}
传递进来的参数a是实参对象的副本,要调用构造函数来构造这个副本,而且函数结束后要调用析构函数来释放这个副本,在空间和时间上都造成了浪费,所以函数参数为类对象的情况,推荐用引用。但按引用传递,造成了安全隐患,通过函数参数的引用可以修改实参的内部数据成员,所以用const来保护实参。
void h(const A & a){
…………
…………
}
(4)const修饰函数返回值
仅返回指针或者引用才有用,保护指针指向的内容或引用的内容不被修改。目的使得函数调用表达式不能作为左值
(5)const在类中
const 成员变量:
const成员函数:
(6)const 修饰类对象
,定义常量对象:
class myClass{
public:
void cc() const {
}
void add(){
}
};
int main()
{
const myClass *m1 = new myClass;
m1->cc();
myClass * m2 = new myClass;
m2->add();
m2->cc();
return 0;
}