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;
一、const char *ptr;
定义一个指向字符常量的指针,这里,ptr是一个指向 char* 类型的常量,所以不能用ptr来修改所指向的内容,*ptr的值为const,不能修改。但是ptr的声明并不意味着它指向的值实际上就是一个常量,而只是意味着对ptr而言,这个值是常量。
二、char const *ptr;
此种写法和const char *等价。
三、char * const ptr;
定义一个指向字符的指针常数,即const指针,实验得知,不能修改ptr指针,但是可以修改该指针指向的内容。
我们可以看一道以前Google的笔试题:
const char *p="hello";
foo(&p); // 函数foo(const char **pp)下面说法正确的是[]
- A.函数foo()不能改变p指向的字符串内容。
- B.函数foo()不能使指针p指向malloc生成的地址。
- C.函数foo()可以使p指向新的字符串常量。
- D.函数foo()可以把p赋值为 NULL。
至于这道题的答案是众说纷纭。针对上面这道题,我们可以用下面的程序测试:
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
void foo(const char **pp)
{
// *pp=NULL;
// *pp="Hello world!";*pp = (char *) malloc(10);snprintf(*pp, 10, "hi google!");
// (*pp)[1] = 'x';
}
int
main()
{const char *p="hello";printf("before foo %s/n",p);foo(&p);printf("after foo %s/n",p);p[1] = 'x';return;
}
结论如下:
- 在foo函数中,可以使main函数中p指向的新的字符串常量。
- 在foo函数中,可以使main函数中的p指向NULL。
- 在foo函数中,可以使main函数中的p指向由malloc生成的内存块,并可以在main中用free释放,但是会有警告。但是注意,即使在foo中让p指向了由malloc生成的内存块,但是仍旧不能用p[1]='x';这样的语句改变p指向的内容。
- 在foo中,不能用(*pp)[1]='x';这样的语句改变p的内容。
所以,感觉gcc只是根据const的字面的意思对其作了限制,即对于const char*p这样的指针,不管后来p实际指向malloc的内存或者常量的内存,均不能用p[1]='x'这样的语句改变其内容。但是很奇怪,在foo里面,对p指向malloc的内存后,可以用snprintf之类的函数修改其内容。
C++形参中const char * 与 char * 的区别
上面在函数调用时,函数的形参出现const常量,那么const char *和char*在做函数参数的时候有什么区别呢?
参考https://www.cnblogs.com/stones-dream/p/9553434.html
例如strlen()函数,它的定义为: size_t strlen( const char *str);
那么将形参设置为const的到底有什么好处呢?网络上经常的回答是:这样将把形参限定为常量,使得我们不能修改它。总感觉这种说法似乎是明白了,但再仔细的想下,总觉得少点什么。
在我看来,这样做的好处有2点:
第一, 保证了实参不能被修改,增加了安全性。
第二, 扩大了该函数的参数的接收范围,使得函数更具通用性。
而第二点,在我看来,是让我恍然大悟的,终于理解了const的作用。
从下边这个例子中,我们可以看出这两点好处;写一个函数length(),实现与strlen()相同的功能,然后进行调用,代码如下:
#include <string>
#include <stdio.h>
#include <tchar.h>
#include <iostream>
using namespace std;
void length(char *s)
{ int count = 0; for (;*s++ !='/0';count++); printf("common length: %d/n",count);
}
int _tmain(int argc, _TCHAR* argv[])
{ char str1[] = "you are a boy 1!"; length(str1); //字符数组 length("you are a boy2 !"); //常量char *s = "you are a boy 3!"; length(s); //字符指针return 0;
}
上面程序可以正确的编译运行,并且输出3行“common length:16”。
但是,如果我想计算一下一个string类型的长度呢?因为length(char *s)的接受参数为char *,我们可以使用stirng类的c_str()方法返回string的字符指针(char *),于是,我们这样操作:
string str2 = "you are a boy 5!";length(str2.c_str());
上边的做法看起来没什么错误,可是这样是通不过编译的,因为str2.c_str()返回的是const char *,而我们的length接受的是char *,const char * 是不能转换成 char *的。
因为const char * s 表示其指针所指向的内容是只读的,不能被修改,而char * s指向的内容是可以修改的,把一个常量指针转换成普通的指针,这显然是不符合逻辑的。关于常量指针和指针常量以及普通的指针的区别,请查阅相关文档。
我们再回过头来看length的需求,length的目的是计算出字符串的长度,它不对字符串本身做修改,对于类似的这种需求的函数(不修改实参),我们应该而且强烈推荐使用const来修饰形参,这也是一种良好的编码习惯。
很明显的看出,如果这样定义:void length(char *s),它将不能接受常量字符指针与常量字符数组。这正是使用const定义形参的第2个好处:扩大了该函数的参数的接收范围;使得函数更具通用性。
常量字符指针是指:str2.c_str()或者str2.data()的返回值,或者自己定义的
常量字符数组是指:const char str1[] = "you are a boy 1!";