首页 > 代码库 > const用法
const用法
(1)可以定义 const 常量
(2)const 可以修饰函数的参数、返回值.
详细内容:
1、什么是const?
常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。(当然,我们可以偷梁换柱进行更新:)
2、为什么引入const?
const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点。
3、cons有什么主要的作用?
(1)可以定义const常量,具有不可变性。例如:
const int Max=100; int Array[Max];
(2)便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。例如: void f(const int i) { .........} 编译器就会知道i是一个常量,不允许修改;(3)可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。同宏定义一样,可以做到不变则已,一变都变!如(1)中,如果想修改Max的内容,只需要:const int Max=you want;即可!
(4)可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。还是上面的例子,如果在函数体内修改了i,编译器就会报错;例如:
void f(const int i) { i=10;//error! }
(5)为函数重载提供了一个参考。
class A { ......
void f(int i) {......} //一个函数
void f(int i) const {......} //上一个函数的重载 ......
};
(6)可以节省空间,避免不必要的内存分配。例如:
#define PI 3.14159 //常量宏
const doulbe Pi=3.14159; //此时并未将Pi放入ROM中 ......
double i=Pi; //此时为Pi分配内存,以后不再分配!
double I=PI; //编译期间进行宏替换,分配内存
double j=Pi; //没有内存分配
double J=PI; //再进行宏替换,又一次分配内存!
const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。
(7)提高了效率。编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
4、如何使用const?
(1)修饰一般常量一般常量是指简单类型的常量。这种常量在定义时,修饰符const可以用在类型说明符前,也可以用在类型说明符后。例如:
int const x=2; 或 const int x=2;
(2)修饰常数组定义或说明一个常数组可采用如下格式:
int const a[5]={1, 2, 3, 4, 5};
const int a[5]={1, 2, 3, 4, 5};
(3)修饰常对象常对象是指对象常量,定义格式如下:
class A; const A a;
A const a; 定义常对象时,同样要进行初始化,并且该对象不能再被更新,修饰符const可以放在类名后面,也可以放在类名前面。
(4)修饰常指针
const int *A; //const修饰指向的对象,A可变,A指向的对象不可变
int const *A; //const修饰指向的对象,A可变,A指向的对象不可变
int *const A; //const修饰指针A, A不可变,A指向的对象可变
const int *const A;//指针A和A指向的对象都不可变
(5)修饰常引用使用const修饰符也可以说明引用,被说明的引用为常引用,该引用所引用的对象不能被更新。其定义格式如下:
const double & v;
(6)修饰函数的常参数 const修饰符也可以修饰函数的传递参数,格式如下:
void Fun(const int Var); 告诉编译器Var在函数体中的无法改变,从而防止了使用者的一些无意的或错误的修改。
(7)修饰函数的返回值: const修饰符也可以修饰函数的返回值,是返回值不可被改变,格式如下:
const int Fun1(); const MyClass Fun2();
(8)修饰类的成员函数: const修饰符也可以修饰类的成员函数,格式如下:
class ClassName {
public:
int Fun() const; .....
};这样,在调用函数Fun时就不能修改类里面的数据
(9)在另一连接文件中引用const常量
extern const int i;//正确的引用
extern const int j=10;//错误!常量不可以被再次赋值另外,还要注意,常量必须初始化!例如: const int i=5;
5、几点值得讨论的地方:
(1)const究竟意味着什么?
说了这么多,你认为const意味着什么?一种修饰符?接口抽象?一种新类型?也许都是,在Stroustup最初引入这个关键字时,只是为对象放入ROM做出了一种可能,对于const对象,C++既允许对其进行静态初始化,也允许对他进行动态初始化。理想的const对象应该在其构造函数完成之前都是可写的,在析够函数执行开始后也都是可写的,换句话说,const对象具有从构造函数完成到析够函数执行之前的不变性,如果违反了这条规则,结果都是未定义的!虽然我们把const放入ROM中,但这并不能够保证const的任何形式的堕落,我们后面会给出具体的办法。无论const对象被放入ROM中,还是通过存储保护机制加以保护,都只能保证,对于用户而言这个对象没有改变。换句话说,废料收集器(我们以后会详细讨论,这就一笔带过)或数据库系统对一个const的修改怎没有任何问题。
(2)位元const V.S. 抽象const?
对于关键字const的解释有好几种方式,最常见的就是位元const 和抽象const。下面我们看一个例子: class A { public: ...... A f(const A& a); ...... }; 如果采用抽象const进行解释,那就是f函数不会去改变所引用对象的抽象值,如果采用位元const进行解释,那就成了f函数不会去改变所引用对象的任何位元。我们可以看到位元解释正是c++对const问题的定义,const成员函数不被允许修改它所在对象的任何一个数据成员。为什么这样呢?因为使用位元const有2个好处:最大的好处是可以很容易地检测到违反位元const规定的事件:编译器只用去寻找有没有对数据成员的赋值就可以了。另外,如果我们采用了位元const,那么,对于一些比较简单的const对象,我们就可以把它安全的放入ROM中,对于一些程序而言,这无疑是一个很重要的优化方式。(关于优化处理,我们到时候专门进行讨论)当然,位元const也有缺点,要不然,抽象const也就没有产生的必要了。首先,位元const的抽象性比抽象const的级别更低!实际上,大家都知道,一个库接口的抽象性级别越低,使用这个库就越困难。其次,使用位元const的库接口会暴露库的一些实现细节,而这往往会带来一些负面效应。所以,在库接口和程序实现细节上,我们都应该采用抽象const。有时,我们可能希望对const做出一些其它的解释,那么,就要注意了,目前,大多数对const的解释都是类型不安全的,这里我们就不举例子了,你可以自己考虑一下,总之,我们尽量避免对const的重新解释。
(3)放在类内部的常量有什么限制?
看看下面这个例子:
class A {
private:
const int c3 = 7; // ???
static int c4 = 7; // ???
static const float c5 = 7; // ??? ......
};
你认为上面的3句对吗?呵呵,都不对!使用这种类内部的初始化语法的时候,常量必须是被一个常量表达式初始化的整型或枚举类型,而且必须是static和const形式。这显然是一个很严重的限制!那么,我们的标准委员会为什么做这样的规定呢?一般来说,类在一个头文件中被声明,而头文件被包含到许多互相调用的单元去。但是,为了避免复杂的编译器规则,C++要求每一个对象只有一个单独的定义。如果C++允许在类内部定义一个和对象一样占据内存的实体的话,这种规则就被破坏了。
(4)如何初始化类内部的常量?
一种方法就是static 和 const 并用,在内部初始化,如上面的例子;另一个很常见的方法就是初始化列表:
class A {
public:
A(int i=0):test(i) {}
private:
const int i;
};还有一种方式就是在外部初始化,例如:
class A {
public:
A() {}
private:
static const int i;//注意必须是静态的!
};
const int A::i=3;
(5)常量与数组的组合有什么特殊吗?我们给出下面的代码:
const int size[3]={10,20,50};
int array[size[2]];
有什么问题吗?对了,编译通不过!为什么呢?
Const可以用于集合,但编译器不能把一个集合存放在它的符号表里,所以必须分配内存。在这种情况下,const意味着"不能改变的一块存储"。然而,其值在编译时不能被使用,因为编译器在编译时不需要知道存储的内容。自然,作为数组的大小就不行了:)你再看看下面的例子:
class A {
public:
A(int i=0):test[2]({1,2}) {}//你认为行吗?
private:
const int test[2];
};
vc6下编译通不过,为什么呢?关于这个问题,前些时间,njboy问我是怎么回事?我反问他:"你认为呢?"他想了想,给出了一下解释,大家可以看看:我们知道编译器堆初始化列表的操作是在构造函数之内,显式调用可用代码之前,初始化的次序依据数据声明的次序。初始化时机应该没有什么问题,那么就只有是编译器对数组做了什么手脚!其实做什么手脚,我也不知道,我只好对他进行猜测:编译器搜索到test发现是一个非静态的数组,于是,为他分配内存空间,这里需要注意了,它应该是一下分配完,并非先分配test[0],然后利用初始化列表初始化,再分配test[1],这就导致数组的初始化实际上是赋值!然而,常量不允许赋值,所以无法通过。呵呵,看了这一段冠冕堂皇的话,真让我笑死了!njboy别怪我揭你短呀:)我对此的解释是这样的:C++标准有一个规定,不允许无序对象在类内部初始化,数组显然是一个无序的,所以这样的初始化是错误的!对于他,只能在类的外部进行初始化,如果想让它通过,只需要声明为静态的,然后初始化。这里我们看到,常量与数组的组合没有什么特殊!一切都是数组惹的祸!
(6)this指针是不是const类型的?
this指针是一个很重要的概念,那该如何理解她呢?也许这个话题太大了,那我们缩小一些:this指针是个什么类型的?这要看具体情况:如果在非const成员函数中,this指针只是一个类类型的;如果在const成员函数中,this指针是一个const类类型的;如果在volatile成员函数中,this指针就是一个volatile类类型的。
(7)const到底是不是一个重载的参考对象?
先看一下下面的例子:
class A {
......
void f(int i) {......}//一个函数
void f(int i) const {......}//上一个函数的重载
......
}; 上面是重载是没有问题的了,那么下面的呢?
class A {
......
void f(int i) {......}//一个函数
void f(const int i) {......}//?????
......
}; 这个是错误的,编译通不过。那么是不是说明内部参数的const不予重载呢?再看下面的例子:
class A {
......
void f(int& ) {......}//一个函数
void f(const int& ) {......}//?????
......
}; 这个程序是正确的,看来上面的结论是错误的。为什么会这样呢?这要涉及到接口的透明度问题。按值传递时,对用户而言,这是透明的,用户不知道函数对形参做了什么手脚,在这种情况下进行重载是没有意义的,所以规定不能重载!当指针或引用被引入时,用户就会对函数的操作有了一定的了解,不再是透明的了,这时重载是有意义的,所以规定可以重载。
(8)什么情况下为const分配内存?
以下是我想到的可能情况,当然,有的编译器进行了优化,可能不分配内存。
A、作为非静态的类成员时;
B、用于集合时;
C、被取地址时;
D、在main函数体内部通过函数来获得值时;
E、const的 class或struct有用户定义的构造函数、析构函数或基类时;。
F、当const的长度比计算机字长还长时;
G、参数中的const;
H、使用了extern时。不知道还有没有其他情况,欢迎高手指点:)
(9)临时变量到底是不是常量?
很多情况下,编译器必须建立临时对象。像其他任何对象一样,它们需要存储空间而且必须被构造和删除。区别是我们从来看不到编译器负责决定它们的去留以及它们存在的细节。对于C++标准草案而言:临时对象自动地成为常量。因为我们通常接触不到临时对象,不能使用与之相关的信息,所以告诉临时对象做一些改变有可能会出错。当然,这与编译器有关,例如:vc6、vc7都对此作了扩展,所以,用临时对象做左值,编译器并没有报错。
(10)与static搭配会不会有问题?假设有一个类:
class A {
public:
......
static void f() const { ......}
......
}; 我们发现编译器会报错,因为在这种情况下static不能够与const共存!为什么呢?因为static没有this指针,但是const修饰this指针,所以...
(11)如何修改常量?
有时候我们却不得不对类内的数据进行修改,但是我们的接口却被声明了const,那该怎么处理呢?我对这个问题的看法如下:
1)标准用法:
mutable class A {
public:
A(int i=0):test(i) { }
void SetValue(int i)const { test=i; }
private: mutable int test;//这里处理!
};
2)强制转换:
const_cast class A {
public:
A(int i=0):test(i) { }
void SetValue(int i)const {
const_cast (test)=i;
}//这里处理!
private:
int test;
};
3)灵活的指针:
int* class A {
public:
A(int i=0):test(i) { }
void SetValue(int i)const { *test=i; }
private:
int* test; //这里处理!
};
4)未定义的处理
class A {
public:
A(int i=0):test(i) { }
void SetValue(int i)const {
int *p=(int*)&test; *p=i;
}//这里处理!
private:
int test;
};注意,这里虽然说可以这样修改,但结果是未定义的,避免使用!
5)内部处理:this指针
class A {
public:
A(int i=0):test(i) { }
void SetValue(int i)const {
((A*)this)->test=i;
}//这里处理!
private:
int test;
};
6)最另类的处理:空间布局
class A {
public:
A(int i=0):test(i),c(‘a‘) { }
private:
char c;
const int test;
};
int main()
{
A a(3);
A* pa=&a;
char* p=(char*)pa;
int* pi=(int*)(p+4);//利用边缘调整
*pi=5;//此处改变了test的值!
return 0;
}
虽然我给出了6中方法,但是我只是想说明如何更改,但出了第一种用法之外,另外5种用法,我们并不提倡,不要因为我这么写了,你就这么用,否则,我真是要误人子弟了:)
(12)最后我们来讨论一下常量对象的动态创建。既然编译器可以动态初始化常量,就自然可以动态创建,例如:
const int* pi=new const int(10); 这里要注意2点:
1)const对象必须被初始化!所以(10)是不能够少的。
2)new返回的指针必须是const类型的。 那么我们可不可以动态创建一个数组呢? 答案是否定的,因为new内置类型的数组,不能被初始化。 这里我们忽视了数组是类类型的,同样对于类内部数组初始化我们也做出了这样的忽视,因为这涉及到数组的问题,我们以后再讨论。
c语言中的const关键字
1, 基本的const
1.1 const和变量的初始化
如果在定义const自动变量时没有进行初始化,
那么就没法直接进行初始化,而只能通过指针间接进行初始化。
int
main()
{
const int a;
a = 9;
//这里无法通过编译
//错误:向只读变量 ‘a‘ 赋值
//但如果写成这样 const int a = 9;将顺利通过编译。
return 0;
}
1.2 以下定义是相同的
int const a = 9;
const int a = 9;
记住:const和基本自动变量使用,定义时可以交换位置。
在和指针一起定义某个变量时,不能交换。
1.3 const并非无法修改
无法直接修改const的变量,但可以通过指针的方式
间接修改。
void
test1(void)
{
int *pi;
int const a = 9;
printf("int const a = %d\n", a);
pi = &a;
*pi = 2;
printf("haha, I change it a = %d\n", a);
}
2, const 和指针
const和指针在一起使用时,容易搞混,
在网上找到一个识别的方法觉得用起来很不错:
舍弃法识破const。
1)首先舍弃const,得到一个普通的声明;
2)若有多个const从最左边的const开始去。
3)然后舍弃const右面的const标志以及其它的关键字;
4)接下来就是将const右面所有的*和变量用一个新变量代替,
单个字符不用被取代,那么这个新变量就是const作用对象。
下面先给出这几个表达式的解释,后面用例子说明。
const int *A; //修饰指向的对象,A可变,A指向的对象不可变
int const *A; //修饰指向的对象,A可变,A指向的对象不可变
int *const A; //修饰指针A, A不可变,A指向的对象可变
const int *const A; //指针A和A指向的对象都不可变
2.1 const int *A;和 int const *A;
按照上面的3条,这两个的表达式的作用应该是一样的。
const的作用对象应该是*A这个整体。也就是作用于A指向的变量的值。
61 void
62 test4(void)
63 {
64 int num=12;
65 const int *A=#
66 (*A)++; //error: 令只读位置自增
67 printf("result=%d\n",*A);
68 }
61-68行可以看出,const int *A的确修饰的是*A这个整体,也就是
指针指向的值。
下面的这个例子说明了,虽然无法改变*A的值,但是可以改变A指针本身。
71 void
72 test5(void)
73 {
74 int a[] = {1,2,3,4,5};
75 const int *ar = &a;
76
77 *ar++; //ok
78 printf("result=%d\n",*ar); //result=2
79 }
2.2 int *const a; 和 const *int a;
const *int a; //这样的定义格式是错误的,指针不知道是什么类型。
int *const a;
按照上面的规则我可以判断,该const是修饰的a,而a被定义为一个指针。
所以他限定的对象是a这个指针。也就是说a的指针值不能修改。
#include<stdio.h>
int
main(void)
{
int a[] = {1,2,3,4,5};
int *const ar = a;
*ar++; //error
//若是这样写,就对了:
//*ar = 9; // ok
printf("result=%d\n",*ar); //result=2
return 0;
}
编译无法通过,出现错误:
test2.c: In function ‘main‘:
test2.c:9: 错误:令只读变量 ‘ar‘ 自增
2.3 const int *const a; //指针A和A指向的对象都不可变
通过分析知道,该定义中const限制了*a和a。也就是这
两个(作为整体)都不能改变。
1 #include<stdio.h>
2
3 int
4 main(void)
5 {
6 int a[] = {1,2,3,4,5};
7 const int *const ar = a;
8
9 //*ar++; //error
10 *ar = 9; // error
11 printf("result=%d\n",*ar); //result=2
12 return 0;
13 }
3, 作为参数时的const
17 void
18 testpc(const char *s, const int *a)
19 {
20 if (*a == 3) {
21 a = 0; //改变其地址允许
22 s[1] = "a"; //改变*s的值,出错
23 }
24 }
根据规则,这样的赋值是错误的,但如果改变其地址却可以。
const int *a;的意思是不能改变 *a这个整体的值。
const是一个C语言的关键字,它限定一个变量不允许被改变。使用const在一定程度上可以提高程序的健壮性,另外,在观看别人代码的时候,清晰理解const所起的作用,对理解对方的程序也有一些帮助。
虽然这听起来很简单,但实际上,const的使用也是c语言中一个比较微妙的地方,微妙在何处呢?请看下面几个问题。
问题:const变量 & 常量
为什么我象下面的例子一样用一个const变量来初始化数组,ANSI C的编译器会报告一个错误呢?
const int n = 5;
int a[n];
答案与分析:
1)、这个问题讨论的是"常量"与"只读变量"的区别。常量肯定是只读的,例如5, "abc",等,肯定是只读的,因为程序中根本没有地方存放它的值,当然也就不能够去修改它。而"只读变量"则是在内存中开辟一个地方来存放它的值,只不过这个值由编译器限定不允许被修改。C语言关键字const就是用来限定一个变量不允许被改变的修饰符(Qualifier)。上述代码中变量n被修饰为只读变量,可惜再怎么修饰也不是常量。而ANSI C规定数组定义时维度必须是"常量","只读变量"也是不可以的。
2)、注意:在ANSI C中,这种写法是错误的,因为数组的大小应该是个常量,而const int n,n只是一个变量(常量 != 不可变的变量,但在标准C++中,这样定义的是一个常量,这种写法是对的),实际上,根据编译过程及内存分配来看,这种用法本来就应该是合理的,只是 ANSI C对数组的规定限制了它。
3)、那么,在ANSI C 语言中用什么来定义常量呢?答案是enum类型和#define宏,这两个都可以用来定义常量。
问题:const变量 & const 限定的内容
下面的代码编译器会报一个错误,请问,哪一个语句是错误的呢?
typedef char * pStr;
char string[4] = "abc";
const char *p1 = string;
const pStr p2 = string;
p1++;
p2++;
答案与分析:
问题出在p2++上。
1)、const使用的基本形式: const char m; 限定m不可变。
2)、替换1式中的m, const char *pm; 限定*pm不可变,当然pm是可变的,因此问题中p1++是对的。
3)、替换1式char, const newType m; 限定m不可变,问题中的charptr就是一种新类型,因此问题中p2不可变,p2++是错误的。
问题:const变量 & 字符串常量
请问下面的代码有什么问题?
char *p = "i‘m hungry!";
p[0]= ‘I‘;
答案与分析:
上面的代码可能会造成内存的非法写操作。分析如下, "i‘m hungry"实质上是字符串常量,而常量往往被编译器放在只读的内存区,不可写。p初始指向这个只读的内存区,而p[0] = ‘I‘则企图去写这个地方,编译器当然不会答应。
问题:const变量 & 字符串常量2
请问char a[3] = "abc" 合法吗?使用它有什么隐患?
答案与分析:
在标准C中这是合法的,但是它的生存环境非常狭小;它定义一个大小为3的数组,初始化为"abc"。注意,它没有通常的字符串终止符‘\0‘,因此这个数组只是看起来像C语言中的字符串,实质上却不是,因此所有对字符串进行处理的函数,比如strcpy、printf等,都不能够被使用在这个假字符串上。
问题5:const & 指针
类型声明中const用来修饰一个常量,有如下两种写法,那么,请问,下面分别用const限定不可变的内容是什么?
1)、const在前面
const int nValue; //nValue是const
const char *pContent; //*pContent是const, pContent可变
const (char *) pContent;//pContent是const,*pContent可变
char* const pContent; //pContent是const,*pContent可变
const char* const pContent; //pContent和*pContent都是const
2)、const在后面,与上面的声明对等
int const nValue; // nValue是const
char const * pContent;// *pContent是const, pContent可变
(char *) const pContent;//pContent是const,*pContent可变
char* const pContent;// pContent是const,*pContent可变
char const* const pContent;// pContent和*pContent都是const
答案与分析:
const和指针一起使用是C语言中一个很常见的困惑之处,在实际开发中,特别是在看别人代码的时候,常常会因为这样而不好判断作者的意图,下面讲一下我的判断原则:
沿着*号划一条线,如果const位于*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于*的右侧,const就是修饰指针本身,即指针本身是常量。你可以根据这个规则来看上面声明的实际意义,相信定会一目了然。
另外,需要注意:对于const (char *) ; 因为char *是一个整体,相当于一个类型(如 char),因此,这是限定指针是const。
另=======
const用于函数时出现三个位置:
例如:
const returnVal function (const list_array)const;
第一个const意思是:返回值是常量
第二个const意思是:函数过程中不能修改list_array的值
第三个const意思是:函数过程不能隐式的修改function参数的值
===
zzhttp://publishblog.blogchina.com/blog/tb.b?diaryID=3217823
const char*, char const*, char*const的区别问题几乎是C++面试中每次都会有的题目。
Bjarne在他的The C++ Programming Language里面给出过一个助记的方法:把一个声明从右向左读。
char * const cp; ( * 读成 pointer to ) :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 prt1 = stringprt1;
其中,ptr1是一个常量指针。因此,下面赋值是非法的。 ptr1 = stringprt2;
而下面的赋值是合法的: *ptr1 = "m";
因为指针ptr1所指向的变量是可以更新的,不可更新的是常量指针ptr1所指的方向(别的字符串)。
下面定义了一个指向字符串常量的指针:
const * ptr2 = stringprt1;
其中,ptr2是一个指向字符串常量的指针。ptr2所指向的字符串不能更新的,而ptr2是可以更新的。因此,
*ptr2 = "x"; 是非法的,而: ptr2 = stringptr2; 是合法的。
所以,在使用const修饰指针时,应该注意const的位置。定义一个指向字符串的指针常量和定义一个指向字符串常量的指针时,const修饰符的位置不同,前者const放在*和指针名之间,后者const放在类型说明符前。
const在C语言中算是一个比较新的描述符,我们称之为常量修饰符,意即其所修饰
的对象为常量(immutable)。
我们来分情况看语法上它该如何被使用。
1、函数体内修饰局部变量。
例:
void func(){
const int a=0;
}
首先,我们先把const这个单词忽略不看,那么a是一个int类型的局部自动变量,
我们给它赋予初始值0。
然后再看const.
const作为一个类型限定词,和int有相同的地位。
const int a;
int const a;
是等价的。于是此处我们一定要清晰的明白,const修饰的对象是谁,是a,和int没
有关系。const 要求他所修饰的对象为常量,不可被改变,不可被赋值,不可作为
左值(l-value)。
这样的写法也是错误的。
const int a;
a=0;
这是一个很常见的使用方式:
const double pi=3.14;
在程序的后面如果企图对pi再次赋值或者修改就会出错。
然后看一个稍微复杂的例子。
const int* p;
还是先去掉const 修饰符号。
注意,下面两个是等价的。
int* p;
int *p;
其实我们想要说的是,*p是int类型。那么显然,p就是指向int的指针。
同理
const int* p;
其实等价于
const int (*p);
int const (*p);
即,*p是常量。也就是说,p指向的数据是常量。
于是
p+=8; //合法
*p=3; //非法,p指向的数据是常量。
那么如何声明一个自身是常量指针呢?方法是让const尽可能的靠近p;
int* const p;
const右面只有p,显然,它修饰的是p,说明p不可被更改。然后把const去掉,可以
看出p是一个指向 int形式变量的指针。
于是
p+=8; //非法
*p=3; //合法
再看一个更复杂的例子,它是上面二者的综合
const int* const p;
说明p自己是常量,且p指向的变量也是常量。
于是
p+=8; //非法
*p=3; //非法
const 还有一个作用就是用于修饰常量静态字符串。
例如:
const char* name=David;
如果没有const,我们可能会在后面有意无意的写name[4]=‘x‘这样的语句,这样会
导致对只读内存区域的赋值,然后程序会立刻异常终止。有了 const,这个错误就
能在程序被编译的时候就立即检查出来,这就是const的好处。让逻辑错误在编译
期被发现。
const 还可以用来修饰数组
const char s[]=David;
与上面有类似的作用。
2、在函数声明时修饰参数
来看实际中的一个例子。
NAME
memmove -- copy byte string
LIBRARY
Standard C Library (libc, -lc)
SYNOPSIS
#include
void *
memmove(void *dst, const void *src, size_t len);
这是标准库中的一个函数,用于按字节方式复制字符串(内存)。
它的第一个参数,是将字符串复制到哪里去(dest),是目的地,这段内存区域必须
是可写。
它的第二个参数,是要将什么样的字符串复制出去,我们对这段内存区域只做读
取,不写。
于是,我们站在这个函数自己的角度来看,src 这个指针,它所指向的内存内所存
储的数据在整个函数执行的过程中是不变。于是src所指向的内容是常量。于是就
需要用const修饰。
例如,我们这里这样使用它。
const char* s=hello;
char buf[100];
memmove(buf,s,6); //这里其实应该用strcpy或memcpy更好
如果我们反过来写,
memmove(s,buf,6);
那么编译器一定会报错。事实是我们经常会把各种函数的参数顺序写反。事实是编
译器在此时帮了我们大忙。如果编译器静悄悄的不报错,(在函数声明处去掉
const即可),那么这个程序在运行的时候一定会崩溃。
这里还要说明的一点是在函数参数声明中const一般用来声明指针而不是变量本身。
例如,上面的size_t len,在函数实现的时候可以完全不用更改len的值,那么是否
应该把len也声明为常量呢?可以,可以这么做。我们来分析这么做有什么优劣。
如果加了const,那么对于这个函数的实现者,可以防止他在实现这个函数的时候修
改不需要修改的值(len),这样很好。
但是对于这个函数的使用者,
1。这个修饰符号毫无意义,我们可以传递一个常量整数或者一个非常量整数过
去,反正对方获得的只是我们传递的一个copy。
2。暴露了实现。我不需要知道你在实现这个函数的时候是否修改过len的值。
所以,const一般只用来修饰指针。
再看一个复杂的例子
int execv(const char *path, char *const argv[]);
着重看后面这个,argv.它代表什么。
如果去掉const,我们可以看出
char * argv[];
argv是一个数组,它的每个元素都是char *类型的指针。
如果加上const.那么const修饰的是谁呢?他修饰的是一个数组,argv[],意思就是
说这个数组的元素是只读的。那么数组的元素的是什么类型呢?是char *类型的指
针.也就是说指针是常量,而它指向的数据不是。
于是
argv[1]=NULL; //非法
argv[0][0]=‘a‘; //合法
3、全局变量。
我们的原则依然是,尽可能少的使用全局变量。
我们的第二条规则则是,尽可能多的使用const。
如果一个全局变量只在本文件中使用,那么用法和前面所说的函数局部变量没有什
么区别。
如果它要在多个文件间共享,那么就牵扯到一个存储类型的问题。
有两种方式。
1.使用extern
例如
/* file1.h */
extern const double pi;
/* file1.c */
const double pi=3.14;
然后其他需要使用pi这个变量的,包含file1.h
#include file1.h
或者,自己把那句声明复制一遍就好。
这样做的结果是,整个程序链接完后,所有需要使用pi这个变量的共享一个存储区域。
2.使用static,静态外部存储类
/* constant.h */
static const pi=3.14;
需要使用这个变量的*.c文件中,必须包含这个头文件。
前面的static一定不能少。否则链接的时候会报告说该变量被多次定义。
这样做的结果是,每个包含了constant.h的*.c文件,都有一份该变量自己的copy,
该变量实际上还是被定义了多次,占用了多个存储空间,不过在加了static关键字
后,解决了文件间重定义的冲突。
坏处是浪费了存储空间,导致链接完后的可执行文件变大。但是通常,这个,小小
几字节的变化,不是问题。
好处是,你不用关心这个变量是在哪个文件中被初始化的。
最后,说说const的作用。
const 的好处,是引入了常量的概念,让我们不要去修改不该修改的内存。直接的
作用就是让更多的逻辑错误在编译期被发现。所以我们要尽可能的多使用const。
但是很多人并不习惯使用它,更有甚者,是在整个程序编写/调试完后才补
const。如果是给函数的声明补const,尚好。如果是给全局/局部变量补const,那
么……那么,为时已晚,无非是让代码看起来更漂亮了。关于const的使用,曾有一
个笑话说,const 就像安全套,事前要记牢。如果做完后才想起来该用而忘了用,
呵呵……呵呵……
p.s.C++中的const与C语言还是有很大差别的。不写了,用者自知吧。
const是一个C语言的要害字,它限定一个变量不答应被改变。使用const在一定程度上可以提高程序的健壮性,另外,在观看别人代码的时候,清楚理解const所起的作用,对理解对方的程序也有一些帮助。
虽然这听起来很简单,但实际上,const的使用也是c语言中一个比较微妙的地方,微妙在何处呢?请看下面几个问题。
问题:const变量 & 常量
为什么我象下面的例子一样用一个const变量来初始化数组,ANSI C的编译器会报告一个错误呢?
const int n = 5;
int a[n];
答案与分析:
1)、这个问题讨论的是"常量"与"只读变量"的区别。常量肯定是只读的,例如5, "abc",等,肯定是只读的,因为程序中根本没有地方存放它的值,当然也就不能够去修改它。而"只读变量"则是在内存中开辟一个地方来存放它的值,只不过这个值由编译器限定不答应被修改。C语言要害字const就是用来限定一个变量不答应被改变的修饰符(Qualifier)。上述代码中变量n被修饰为只读变量,可惜再怎么修饰也不是常量。而ANSI C规定数组定义时维度必须是"常量","只读变量"也是不可以的。
2)、注重:在ANSI C中,这种写法是错误的,因为数组的大小应该是个常量,而const int n,n只是一个变量(常量 != 不可变的变量,但在标准C++中,这样定义的是一个常量,这种写法是对的),实际上,根据编译过程及内存分配来看,这种用法本来就应该是合理的,只是ANSI C对数组的规定限制了它。
3)、那么,在ANSI C 语言中用什么来定义常量呢?答案是enum类型和#define宏,这两个都可以用来定义常量。
更多内容请看C/C++进阶技术文档专题,或
问题:const变量 & const 限定的内容
下面的代码编译器会报一个错误,请问,哪一个语句是错误的呢?
typedef char * pStr;
char string[4] = "abc";
const char *p1 = string;
const pStr p2 = string;
p1++;
p2++;
答案与分析:
问题出在p2++上。
1)、const使用的基本形式: const char m;
限定m不可变。
2)、替换1式中的m, const char *pm;
限定*pm不可变,当然pm是可变的,因此问题中p1++是对的。
3)、替换1式char, const newType m;
限定m不可变,问题中的charptr就是一种新类型,因此问题中p2不可变,p2++是错误的。
问题:const变量 & 字符串常量
请问下面的代码有什么问题?
char *p = "i‘m hungry!";
p[0]= ‘I‘;
答案与分析:
上面的代码可能会造成内存的非法写操作。分析如下, "i‘m hungry"实质上是字符串常量,而常量往往被编译器放在只读的内存区,不可写。p初始指向这个只读的内存区,而p[0] = ‘I‘则企图去写这个地方,编译器当然不会答应。
问题:const变量 & 字符串常量2
请问char a[3] = "abc" 合法吗?使用它有什么隐患?
答案与分析:
在标准C中这是合法的,但是它的生存环境非常狭小;它定义一个大小为3的数组,初始化为"abc",,注重,它没有通常的字符串终止符‘\0‘,因此这个数组只是看起来像C语言中的字符串,实质上却不是,因此所有对字符串进行处理的函数,比如strcpy、PRintf等,都不能够被使用在这个假字符串上。
问题5:const & 指针
类型声明中const用来修饰一个常量,有如下两种写法,那么,请问,下面分别用const限定不可变的内容是什么?
1)、const在前面
const int nValue; //nValue是const
const char *pContent; //*pContent是const, pContent可变
const (char *) pContent;//pContent是const,*pContent可变
char* const pContent; //pContent是const,*pContent可变
const char* const pContent; //pContent和*pContent都是const
2)、const在后面,与上面的声明对等
int const nValue; // nValue是const
char const * pContent;// *pContent是const, pContent可变
(char *) const pContent;//pContent是const,*pContent可变
char* const pContent;// pContent是const,*pContent可变
char const* const pContent;// pContent和*pContent都是const
答案与分析:
const和指针一起使用是C语言中一个很常见的困惑之处,在实际开发中,非凡是在看别人代码的时候,经常会因为这样而不好判定作者的意图,下面讲一下我的判定原则:
沿着*号划一条线,const和谁在一边,那么谁就是const,即const限定的元素就是它。你可以根据这个规则来看上面声明的实际意义,相信定会一目了然。
另外,需要注重:对于const (char *) ; 因为char *是一个整体,相当于一个类型(如 char),因此,这是限定指针是const。
一、说明指针常量、指向常量的指针和指向常量的常量指针的含义和区别和共同点。
首先,以上三种概念的共同点就是都是说的指针。
指针也是一种变量,它存储指定类型的变量的内存地址,如可char* 来声明一个字符型指针变量,跟其它变量一样,当其值不可改变时,该指针变量就成为了指针常量,既是常量,它当然一直指向同一个内存地址,而不能被改变。
指向常量的指针顾名思义就是说其指向的那个地址的值不是变量而是常量了,即其指向的内存地址的内容将不能被改变,而指针本身则可以改变。
指向常量的指针常量最好理解,结合以上两者的含义即可知道,其特点就是指针本身的值(即指向的内存地址)和指针指向内存地址的内容均是常量,不能被改变。
二、const的应用举例及说明
//指针常量
char str[5] = "abcd"; //声明一个字符数组
char * const pStr = str; //让pStr指针指向字符数组变量str;pStr该指针常量值(即指向的内存地址)不能被改变。
//分别执行以下语句的结果,可以体现它的特点
pStr = "asdfaf" ; //企图让pStr 指针常量指向另一个地址,结果报错!
*pStr = ‘d‘; //修改pStr指针常量指向的内存地址中第一个字符的内容为d,结果正确,第一个字符被修改。
//指向常量的指针
char str[5] = "abcd"; //声明一个字符数组
const char * pStr = str; //让pStr指针指向字符数组常量str,该字符数组不能被改变;
//分别执行以下语句的结果,可以体现它的特点
pStr = "asdfaf" ; //让pStr 指针常量指向另一个地址,结果正确,pStr不再指向str字符数组常量,而指向了字符串asdfaf的首地址!
*pStr = ‘d‘; //修改pStr指针常量指向的内存地址中第一个字符的内容为d,结果错误,常量不能被修改。
//指向常量的指针常量
char str[5] = "abcd"; //声明一个字符数组
const char * const pStr = str; //让pStr指针指向字符数组常量str,该字符数组不能被改变;
//分别执行以下语句的结果,可以体现它的特点
pStr = "asdfaf" ; //让pStr 指针常量指向另一个地址,结果错误,此时是指针值不能被改变!
*pStr = ‘d‘; //修改pStr指针常量指向的内存地址中第一个字符的内容为d,结果错误,此时是内容不能被改变。
以上说明希望对初学者对const的理解和使用有所帮助!
const 用法总结(C++)
根据个人的学习和理解,下面我将从以下几个分类来进行讨论,如有错误之处,还请各位大虾多多指教!(部分内容直接转载,以供学习和参考)
一、关于一般常量
声明或定义的格式如下:
const <类型说明符> <变量名> = <常量或常量表达式>; [1]
<类型说明符> const <变量名> = <常量或常量表达式>; [2]
[1]和[2]的定义是完全等价的。
例如:
整形int(或其他内置类型:float,double,char)
const int bufSize = 512;
或者
int const bufSize = 512;
因为常量在定义后就不能被修改,所以定义时必须初始化。
bufSize = 128; // error:attempt to write to const object
const string cntStr = "hello!"; // ok:initialized
const i, j = 0; // error: i is uninitialized const
非const变量默认为extern。
const 对象默认为文件的局部变量。要使const变量能够在其他的文件中访问,必须显式地指定它为extern。
例如:
const int bufSize = 512; // 作用域只限于定义此变量的文件
extern const int bufSize = 512; // extern用于扩大作用域,作用域为整个源程序(只有extern 位于函数外部时,才可以含有初始化式)
二、关于数组及结构体
声明或定义的格式如下:
const <类型说明符> <数组名>[<大小>]…… [1]
<类型说明符> const <数组名>[<大小>]…… [2]
[1]和[2]的定义是完全等价的。
例如:
整形int(或其他内置类型:float,double,char)
const int cntIntArr[] = {1,2,3,4,5};
或者
int const cntIntArr[] = {1,2,3,4,5};
struct SI
{
int i1;
int i2;
};
const SI s[] = {{1,2},{3,4}};
// 上面的两个const都是变量集合,编译器会为其分配内存,所以不能在编译期间使用其中的值(例如:int temp[cntIntArr[2]],这样的话编译器会报告不能找到常量表达式)
三、关于引用
声明或定义的格式如下:
const <类型说明符> &<变量名> = …… [1]
<类型说明符> const &<变量名> = …… [2]
[1]和[2]的定义是完全等价的。
例如:
const int i = 128;
const int &r = i;(或者 int const &r = i;)
const 引用就是指向const 对象的引用。
普通引用不能绑定到const 对象,但const 引用可以绑定到非const 对象。
const int ii = 456;
int &rii = ii; // error
int jj = 123;
const int &rjj = jj; // ok
非const 引用只能绑定到与该引用同类型的对象。
const 引用则可以绑定到不同但相关的类型的对象或绑定到右值。
例如:
1.const int &r = 100; // 绑定到字面值常量
2.int i = 50;
const int &r2 = r + i; // 引用r绑定到右值
3.double dVal = 3.1415;
const int &ri = dVal; // 整型引用绑定到double 类型
编译器会把以上代码转换成如下形式的编码:
int temp = dVal; // create temporary int from double
const int &ri = temp; // bind ri to that temporary
四、关于指针
1.指向const 对象的指针(指针所指向的内容为常量)
声明或定义的格式如下(定义时可以不初始化):
const <类型说明符> *<变量名> …… [1]
<类型说明符> const *<变量名> …… [2]
[1]和[2]的定义是完全等价的。
例如:
const int i = 100;
const int *cptr = &i;
或者
int const *cptr = &i; [cptr 是指向int 类型的const 对象的指针]
允许把非const 对象的地址赋给指向const 对象的指针,例如:
double dVal = 3.14; // dVal is a double; its value can be change
const double *cdptr = &dVal; // ok;but can‘t change dVal through cdptr
不能使用指向const 对象的指针修改基础对象。然而如果该指针指向的是一个没const 对象(如cdptr),可用其他方法修改其所指向的对象。
如何将一个const 对象合法地赋给一个普通指针???
例如:
const double dVal = 3.14;
double *ptr = &dVal; // error
double *ptr = const_cast<double*>(&dVal);
// ok: const_cast是C++中标准的强制转换,C语言使用:double *ptr = (double*)&dVal;
2.const 指针(指针本身为常量)
声明或定义的格式如下(定义时必须初始化):
<类型说明符> *const <变量名> = ……
例如:
int errNumb = 0;
int iVal = 10;
int *const curErr = &errNumb; [curErr 是指向int 型对象的const 指针]
指针的指向不能被修改。
curErr = &iVal; // error: curErr is const
指针所指向的基础对象可以修改。
*curErr = 1; // ok:reset value of the object(errNumb) which curErr is bind
3.指向const 对象的const 指针(指针本身和指向的内容均为常量)
声明或定义的格式如下(定义时必须初始化):
const <类型说明符> *const <变量名> = ……
例如:
const double pi = 3.14159;
const double dVal = 3.14;
const double *const pi_ptr = π [pi_ptr 是指向double 类型的const 对象的const 指针]
指针的指向不能被修改。
pi_ptr = &dVal; // error: pi_ptr is const
指针所指向的基础对象也不能被修改。
*pi_ptr = dVal; // error: pi is const
五、关于一般函数
1.修饰函数的参数
class A;
void func1(const int i); // i不能被修改
void func3 (const A &rA); // rA所引用的对象不能被修改
void func2 (const char *pstr); // pstr所指向的内容不能被修改
2.修饰函数的返回值
返回值:const int func1(); // 此处返回int 类型的const值,意思指返回的原函数里的变量的初值不能被修改,但是函数按值返回的这个变量被制成副本,能不能被修改就没有了意义,它可以被赋给任何的const或非const类型变量,完全不需要加上这个const关键字。
[*注意*]但这只对于内部类型而言(因为内部类型返回的肯定是一个值,而不会返回一个变量,不会作为左值使用,否则编译器会报错),对于用户自定义类型,返回值是常量是非常重要的(后面在类里面会谈到)。
返回引用:const int &func2(); // 注意千万不要返回局部对象的引用,否则会报运行时错误:因为一旦函数结束,局部对象被释放,函数返回值指向了一个对程序来说不再有效的内存空间。
返回指针:const int *func3(); // 注意千万不要返回指向局部对象的指针,因为一旦函数结束,局部对象被释放,返回的指针变成了指向一个不再存在的对象的悬垂指针。
六、关于类
class A
{
public:
void func();
void func() const;
const A operator+(const A &) const;
private:
int num1;
mutable int num2;
const size_t size;
};
1.修饰成员变量
const size_t size; // 对于const的成员变量,[1]必须在构造函数里面进行初始化;[2]只能通过初始化成员列表来初始化;[3]试图在构造函数体内对const成员变量进行初始化会引起编译错误。
例如:
A::A(size_t sz):size(sz) // ok:使用初始化成员列表来初始化
{
}
A::A(size_t sz)
2.修饰类成员函数
void func() const; // const成员函数中不允许对数据成员进行修改,如果修改,编译器将报错。如果某成员函数不需要对数据成员进行修改,最好将其声明为const 成员函数,这将大大提高程序的健壮性。
const 为函数重载提供了一个参考
class A
{
public:
void func(); // [1]:一个函数
void func() const; // [2]:上一个函数[1]的重载
……
};
A a(10);
a.func(); // 调用函数[1]
const A b(100);
b.func(); // 调用函数[2]
如何在const成员函数中对成员变量进行修改???
下面提供几种方式(只提倡使用第一种,其他方式不建议使用)
(1)标准方式:mutable
class A
{
public:
A::A(int i):m_data(i){}
void SetValue(int i){ m_data = http://www.mamicode.com/i; }
private:
mutable int m_data; // 这里处理
};
(2)强制转换:static_cast
class A
{
public:
A::A(int i):m_data(i){}
void SetValue(int i)
{ static_cast<int>(m_data) = i; } // 这里处理
private:
int m_data;
};
(3)强制转换:const_cast
class A
{
public:
A::A(int i):m_data(i){}
void SetValue(int i)
{ const_cast<A*>(this)->m_data = http://www.mamicode.com/i; } // 这里处理
private:
int m_data;
};
(4)使用指针:int *
class A
{
public:
A::A(int i):m_data(i){}
void SetValue(int i)
{ *m_data = http://www.mamicode.com/i; } // 这里处理
private:
int *m_data;
};
(5)未定义的处理方式
class A
{
public:
A::A(int i):m_data(i){}
void SetValue(int i)
{ int *p = (int*)&m_data; *p = i } // 这里处理
private:
int m_data;
};
注意:这里虽然说可以修改,但结果是未定义的,避免使用!
3.修饰类对象
const A a; // 类对象a 只能调用const 成员函数,否则编译器报错。
4.修饰类成员函数的返回值
const A operator+(const A &) const; // 前一个const 用来修饰重载函数operator+的返回值,可防止返回值作为左值进行赋值操作。
例如:
A a;
A b;
A c;
a + b = c; // errro: 如果在没有const 修饰返回值的情况下,编译器不会报错。
七、使用const的一些建议
1.要大胆的使用const,这将给你带来无尽的益处,但前提是你必须搞清楚原委;
2.要避免最一般的赋值操作错误,如将const变量赋值,具体可见思考题;
3.在参数中使用const应该使用引用或指针,而不是一般的对象实例,原因同上;
4.const在成员函数中的三种用法(参数、返回值、函数)要很好的使用;
5.不要轻易的将函数的返回值类型定为const;
6.除了重载操作符外一般不要将返回值类型定为对某个对象的const引用;
八、cons有什么主要的作用?
1.可以定义const常量,具有不可变性。
例如:
const int Max=100;
int Array[Max];
2.便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。
例如:
void f(const int i) { .........}
编译器就会知道i是一个常量,不允许修改;
3.可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。
同宏定义一样,可以做到不变则已,一变都变!如(1)中,如果想修改Max的内容,只需要:const int Max=you want;即可!
4.可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。
还是上面的例子,如果在函数体内修改了i,编译器就会报错;
例如:
void f(const int i) { i=10;//error! }
5.为函数重载提供了一个参考。
class A
{
......
void f(int i) {......} file://一个函数
void f(int i) const {......} file://上一个函数的重载
......
};
6.可以节省空间,避免不必要的内存分配。
例如:
#define PI 3.14159 file://常量宏
const doulbe Pi=3.14159; file://此时并未将Pi放入ROM中
......
double i=Pi; file://此时为Pi分配内存,以后不再分配!
double I=PI; file://编译期间进行宏替换,分配内存
double j=Pi; file://没有内存分配
double J=PI; file://再进行宏替换,又一次分配内存!
const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。
7.提高了效率。
编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
{
size = sz; // error:试图在构造函数体内对const成员变量进行初始化
}
沿着*号划一条线,const和谁在一边,那么谁就是const,即const限定的元素就是它。你可以根据这个规则来看上面声明的实际意义,相信定会一目了然。
另外,需要注意:对于const (char *) ; 因为char *是一个整体,相当于一个类型(如 char),因此,这是限定指针是const。
int const nValue; // nValue是const char const * pContent;// *pContent是const, pContent可变 (char *) const pContent;//pContent是const,*pContent可变 char* const pContent;// pContent是const,*pContent可变 char const* const pContent;// pContent和*pContent都是const |
情景一:最简单的const用法
#include<stdio.h> int main() { int const a; a=5; printf("a=%d\n",a); return 0; }如果编译这个c文件,就会报错: 1071.c: In function ‘main‘: 1071.c:5: error: assignment of read-only variable ‘a‘ 显而易见,这是const在搞鬼。 因为声明了const的变量是不能修改的! 如果将源代码修改为如下这样,就没有问题了! #include<stdio.h> int main() { int const a=5; printf("a=%d\n",a); return 0; } |
总结:const声明的变量必须要进行初始化赋值,如果错过这个机会,以后再想给const的变量赋值,可就没门了!切记~
PS:int const和const int是一回事,"颠倒写"都是可以的。以后遇到了别犯晕,呵呵。但是,还是要留个心眼,当const和指针搀和到一起时,这个"颠倒写"的规律可未必成立。
情景二:发明const为了什么?
在const诞生之前,开发者一直使用#define VAR 100来定义一些有特殊用途的类常量,不过这样定义是存在一些劣势的。因此const应运而生,之后开发者可以使用const int VAR=100;来定义类常量了。
至于为什么#define有其劣势,还要读者自己去google下。
情景三:const和指针的配合是噩梦!
你能分辨得清这些声明么: const int *A; int const *A; int *const A; const int *const A const int *A; //修饰指向的对象,A可变,A指向的对象不可变 int const *A; //修饰指向的对象,A可变,A指向的对象不可变 int *const A; //修饰指针A, A不可变,A指向的对象可变 const int *const A; //指针A和A指向的对象都不可变 |
情景四:const int *A
[rocrocket@wupengchong const_test]$ cat test1.c #include<stdio.h> int main() { int num=12; const int *A=# printf("result=%d\n",*A); return 0; }编译执行结果为: [rocrocket@wupengchong const_test]$ cc test1.c [rocrocket@wupengchong const_test]$ ./a.out result=12 接下来,我们动动手脚,在代码中加入了(*A)++;这条语句: [rocrocket@wupengchong const_test]$ cat test1.c #include<stdio.h> int main() { int num=12; const int *A=# (*A)++; printf("result=%d\n",*A); return 0; }编译这个c文件: [rocrocket@wupengchong const_test]$ !cc cc test1.c test1.c: In function ‘main‘: test1.c:6: error: increment of read-only location ‘*A‘ 可以看到,报错了,报错的内容表示"*A"是只读的,不能修改。 我们再修改一下源代码为下面这样: [rocrocket@wupengchong const_test]$ cat test1.c #include<stdio.h> int main() { int num=12; int tmp=100; const int *A=# A=&tmp; printf("result=%d\n",*A); return 0; }编译执行结果为: [rocrocket@wupengchong const_test]$ !cc cc test1.c [rocrocket@wupengchong const_test]$ ./a.out result=100 |
好了,如果你仔细看了这几个小得不能再小的程序,你自己都可以给出结论了!
结论:如果声明了const int *A,那么A值是可以修改的,而*A是不可以修改的。更通俗的说,A指针可以随便指向一个整型,但只要被A盯上了的整型变量在使用*A引用时就不能修改了。
[rocrocket@wupengchong const_test]$ cat test1.c #include<stdio.h> int main() { int num=12; int tmp=100; const int *A=# A=&tmp; tmp=3; printf("result=%d\n",*A); return 0; }编译执行的结果为: [rocrocket@wupengchong const_test]$ !cc cc test1.c [rocrocket@wupengchong const_test]$ ./a.out result=3 |
结论2:即使A指向了tmp,我虽然不能修改*A,但是我仍然是可以用tmp来修改这个值的,完全不管*A的存在。呵呵
情景五:int *const A
[rocrocket@wupengchong const_test]$ cat test1.c #include<stdio.h> int main() { int num=12; int *const A=# printf("result=%d\n",*A); return 0; }编译执行结果为: [rocrocket@wupengchong const_test]$ !cc cc test1.c [rocrocket@wupengchong const_test]$ ./a.out result=12 我们稍微修改下源代码: [rocrocket@wupengchong const_test]$ cat test1.c #include<stdio.h> int main() { int num=12; int tmp=100; int *const A=# A=&tmp; printf("result=%d\n",*A); return 0; }编译时报错了: [rocrocket@wupengchong const_test]$ !cc cc test1.c test1.c: In function ‘main‘: test1.c:7: error: assignment of read-only variable ‘A‘ [rocrocket@wupengchong const_test]$ cat test1.c 可见A本身的值已经不能再变了。 继续修改源代码如下: [rocrocket@wupengchong const_test]$ cat test1.c #include<stdio.h> int main() { int num=12; int *const A=# (*A)=100; printf("result=%d\n",*A); return 0; }编译执行结果为: [rocrocket@wupengchong const_test]$ !cc cc test1.c [rocrocket@wupengchong const_test]$ ./a.out result=100 可以看出,(*A)是可以改变的。 |
结论又可以轻易推出了:int *const A; //const修饰指针A, A不可变,A指向的对象可变
情景六:const int *const A; //指针A和A指向的对象都不可变
[rocrocket@wupengchong const_test]$ cat test1.c #include<stdio.h> int main() { int num=12; int const *const A=# (*A)=100; printf("result=%d\n",*A); return 0; }编译会报错: [rocrocket@wupengchong const_test]$ !cc cc test1.c test1.c: In function ‘main‘: test1.c:6: error: assignment of read-only location ‘*A‘ 改下源代码: [rocrocket@wupengchong const_test]$ cat test1.c #include<stdio.h> int main() { int num=12; int tmp=100; int const *const A=# A=&tmp; printf("result=%d\n",*A); return 0; }编译仍然会报错: [rocrocket@wupengchong const_test]$ !cc cc test1.c test1.c: In function ‘main‘: test1.c:7: error: assignment of read-only variable ‘A‘ |
呵呵,结论很明显了,const int *const A; //指针A和A指向的对象都不可变
当然const int *const A;和int const *const A=#是等价的!
情景七:如果const用在函数形参里呢?是不是又要复杂很多?
答案是NO!一点也不复杂。
来看看这个函数投:int addnum(const int num, int a, int b);
这个函数声明中的第一个形参是const int num,这就表明如果我调用了这个函数,那么第一个实参被传到addnum函数里之后,就不能再做修改了!呵呵 就这么简单。
给个例子吧,让大家能更一目了然:
[rocrocket@wupengchong const_test]$ cat test2.c #include<stdio.h> int addto(const int num, int a, int b) { if(num==1){ return a+b; }else{ return 0; } }
int main(){ int num=100; int a=12,b=22; int res; num=1; res=addto(num,a,b); printf("res=%d\n",res); return 0; }编译执行结果为: [rocrocket@wupengchong const_test]$ !cc cc test2.c [rocrocket@wupengchong const_test]$ ./a.out res=34 如果我修改一下,编译就会出错: [rocrocket@wupengchong const_test]$ cat test2.c #include<stdio.h> int addto(const int num, int a, int b) { if(num==1){ num=3; return a+b; }else{ return 0; } }
int main(){ int num=100; int a=12,b=22; int res; num=1; res=addto(num,a,b); printf("res=%d\n",res); return 0; }编译报错为: [rocrocket@wupengchong const_test]$ !cc cc test2.c test2.c: In function ‘addto‘: test2.c:5: error: assignment of read-only location ‘num‘ |
可见在函数里形参被声明为const的变量也是不能修改的哦!呵呵~
const其实不难,把本文的几个小例子看懂就OK了!
浅析C++"const"常用方法
C++的c*****t可以非常灵活的摆放,例如:c*****t int i、int c*****t i、c*****t int* p、int c*****t *p、int* c*****t p 等等,很多人会因此感到困惑,甚至于不使用c*****t,我也是这样的。作为过来人,有必要说的是,其实c*****t常用的也就这么几种(欢迎大家补遗):
1、修饰变量(variable)
c*****t放在最前面修饰变量,例如:c*****t int i、c*****t int* p,这表明变量的内容不能改变,对i和*p不能进行赋值,例如 i = 20 或 *p = 20,编译器会报错。
c*****t放在指针名称前,例如:int* c*****t p,这表明指针的地址不能改变了,例如:++p是不允许的。
如果既想指针的内容无法改变,也想指针的指向的地址无法改变,则可以这么写:c*****t int* c*****t p,这也是一种常见的用法。
2、修饰函数(method)
c*****t修饰函数常用在修饰返回值为指针的函数体上,例如:
int g = 0;
c*****t int* GetValuePtr()
{
return &g;
}
c*****t char* GetStr()
{
return "Hello";
}
这样就表明返回的指针指向的内容,调用者不要去改动。对于调用方,也只能声明用c*****t修饰的指针去调用,例如:
c*****t int* i = GetValuePtr();
c*****t char* p = GetStr();
如1所示,这样声明指针,当然也就无法修改指针内容了。
3、修饰类成员函数(method of class)
c*****t修饰非类的函数的果效同样作用于类成员函数上,特殊的是类的成员函数还可以在声明的末端用c*****t修饰,例如:
class CMyClass
{
public:
CMyClass();
~CMyClass();
public:
int GetValue() c*****t;
int* GetPtr();
private:
int m_iValue;
};
这样的意思是,在GetValue这个函数体内,是不会有改变类成员变量和调用其它非c*****t修饰函数的举动,例如:
int CMyClass::GetValue() c*****t
{
m_iValue = http://www.mamicode.com/0; //编译器会报错
GetPtr(); //编译器会报错
return m_iValue;
}
所以类的某个函数如果不会去改变类的成员变量,例如只返回某个成员变量的类函数,那么就应该在尾巴处修饰上c*****t。
4、修饰类(class)
例如:c*****t CMyClass o,这时的o对类成员变量和类函数的调用是有限制的:
a、能读取类的成员变量而不能去修改它;
b、只能调用末端有c*****t修饰的函数。
class CMyClass
{
public:
CMyClass()
{
m_iValue = http://www.mamicode.com/0;
m_pPtr = NULL;
}
~CMyClass()
{
}
public:
int GetValue() c*****t
{
return m_iValue;
}
int* GetPtr()
{
return m_pPtr;
}
private:
int* m_pPtr;
int m_iValue;
};
c*****t CMyClass o;
o.GetValue(); //允许使用的方法
o.GetPtr(); //编译出错
5、注意:
使用了c*****t后,编译器会作一些优化,例如:
c*****t int x = 4;
TRACE("%d\n", x);
int* pX = ( int* ) (&x); //打印出来是4。
*pX = 3;
TRACE("%d\n", x); //打印出来的是什么?
打印出来还是4。因为经过编译器优化,对TRACE函数调用时,直接传了固定值(push 4)。在VC2005上以Debug和Release编译均是如此。
c*****t能让编译器更好的理解你的程序,自然她产出的代码也就会更强壮。同时,c*****t也可以理解为程序员之间的小默契,例如你遇到了某个c*****t修饰的函数,你就应该知道应该乖乖地不要去动这个函数的返回值。
C++是一门不安全的语言,所以C++程序员必须要特别小心,利用C++语法提供的每一项特性来加强程序的强壮性。c*****t能加强您程序的强壮性,所以擅以运用类似于c*****t这样的特性(对于Java是final)是程序员的基本功,而更多的对c*****t的体会还需要我们在实践中去领悟。
const用法