const char*, char const*, char*const使用时的区别

案例1:

#include<iostream>
using namespace std;

void main(void)
{
	// char* a 与 char a[] 的区别
	char* a = "abcdef";        // a为一指针,其值可以改变。现在a指向的是一常量字符串
	cout << a << endl;
	a = "ghijkl";              // a现在指向另一常量字符串
	//a[0] = 'z';              // a指向的为常量,编译没问题,但运行出现异常
	cout << a << endl;
	cout << sizeof(a) << endl; // 4

	char b[] = "abcdef";       // b为数组首地址,为一常量,其值不可改变。所指内容不是常量字符串
	cout << b << endl;
	//b = "ghijkl";            // 编译无法通过,b为常量,无法为其指定其它值
	b[0] = 'z';
	cout << b << endl;
	cout << sizeof(b) << endl; // 7

	// ------------------------------------------------------------------------------

	// const char* a 与 char* const a 区别
	char c[] = "abcd";
	char d[] = "efgh";
	const char* pC = c;
	//pC[0] = 'z';             // 编译出错,pC为指向常量的指针,做不能修改指针指向的内容
	cout << pC << endl;
	pC = d;
	cout << pC << endl;        // pC指向的内容不能改,但可以修改指向

	char e[] = "abcd";
	char f[] = "efgh";
	char* const pE = e;
	//pE = f;                  // pE为常量指针,不能改变其指向
	pE[0] = 'z';               // pE为常量指针,虽然不能改变其指向,但可以修改指向内容
	cout << pE << endl;

}

// 总结:(1) char* a这种形式:a为指针,可以改变其指向,
//       其所指向的字符串为常量,不能修改其指向的内容。
//       (2) char a[]这种形式,a为数组名,为常量,不能再
//       指向其它字符串,但其指向的内容不是常量字符串,
//       故可以改变。

//       (1) const char* a 为指向常量的指针,所指内容为常量,
//       不能修改,但可以改变其指向,这种形式还与
//       char const * a等价。
//       (2) char* const a 为指针常量,不能改变其指向,但可
//       以修改其指向的内容。
//       (3) const char* const a 为指向常量的常指针,即不能
//       改变其指向,也不能改变其指向的内容

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只能属于前面的类型。

C++标准规定,const关键字放在类型或变量名之前等价的。

const int n=5;    //same as below

int const m=10;

const int *p;    //same as below  const (int) * p

int const *q;    // (int) const *p

看一道以前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';   // runtime error
}

int main()
{
    const char *p="hello";
    printf("before foo %s/n",p);
    foo(&p);
    printf("after foo %s/n",p);
    p[1] = 'x';    //runtime error 

    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之类的函数修改其内容。

深入理解

const char*p,char const*p,char *const p,const char **p,char const**p,char *const*p,char**const p

一、可能的组合:

(1)const char*p

(2)char const*p

(3)char *const p

(4)const char **p

(5)char const**p

(6)char *const *p

(7)char **const p

当然还有在(5)、(6)、(7)中再插入一个const的若干情况,不过分析了以上7中,其他的就可类推了!

二、理解助记法宝:

1。关键看const 修饰谁。

2。由于没有const *的运算,若出现const*的形式,则const实际上是修饰前面的。

比如:char const*p,由于没有const*运算,则const实际上是修饰前面的char,因此char const*p等价于const char*p。也就是说上面7种情况中,(1)和(2)等价。 同理,(4)和(5)等价。在(6)中,由于没有const*运算,const实际上修饰的是前面的char*,但不能在定义时转换写成 const(char *)*p,因为在定义是"()"是表示函数。

三、深入理解7种组合

(0)程序:在执行时为其开辟的空间皆在内存(RAM)中,而RAM里的内存单元是可读可写 的;指针只是用来指定或定位要操作的数据的工具,只是用来读写RAM里内存单元的工作指针
。若对指针不加任何限定,程序中一个指针可以指向RAM中的任意位置(除了系统敏感区,如操作系统内核所在区域)并对其指向的内存单元进行读和写操作(由RAM的可读可写属性决定);RAM里内存单元的可读可写属性不会因为对工作指针的限定而变化(见下面的第4点),而所有对指针的各种const限定说白了只是对该指针的读写权限 (包括读写位置)进行了限定

(1)char *p:p是一个工作指针,可以用来对任意位置 (非系统敏感区域)进 行读操作和写操作 ,一次读写一个字节(char占一个字节)。

(2)const char*p或者char const *p(因为没有const*p运算,因此const修饰的还是前面的char):可以对任意位置(非系统敏感区域)进行“只读” 操作。(“只读”是相对于char *p来说所限定的内容)

(3)char *const p(const 修饰的是p):只能对“某个固定的位置” 进 行读写操作,并且在定义p时就必须初始化(因为在后面不能执行“p=..”的操作,因此就不能在后面初始化,因此只能在定义时初始化)。(“某个固定的位 置”是相对于char *p来说所限定的内容)

可以总结以上3点为:char *p中的指针p通常是”万能”的工作指针 ,而(2)和(3)只是在(1)的基础上加了些特定的限制 ,这些限制在程序中并不是必须的,只是为了防止程序员的粗心大意而产生事与愿违的错 误。

另外,要明白“每块内存空间都可有名字;每块内存空间内容皆可变(除非有所限) ” 。

比如函数里定义的char s[]="hello";事实上在进程的栈内存里开辟了6个变量共6个字节的空间,其中6个字符变量的名字分别为:s1[0]、s1[1]、 s1[2]、s1[3]、s1[4]、s1[5](内容是‘/0‘)

{

待验证 (见后面问题3的分析) : 还有一个4字节的指针变量s 。不过s是“有所限制”的,属于char *const类型,也就是前面说的   (3)这种情况,s一直指向s[0],    即(*s==s[0]==‘h‘),可以通过*s=‘k‘来改变s所指向的 s[0]的值,但不能执行(char *h=“aaa”;s=h;)来对s另外赋值。

}

(4)上面的(2)和(3)只是对p进行限定,没有也不能对p所指向的空间进行限定,对于"char s[]="hello";const char*p=s;"
虽然不能通过*(p+i)=‘x‘或者p[i]=‘x‘来修改数组元素s[0]~s[4]的值,但可以通过*(s+i)=‘x‘或者s[i]=‘x‘来修改原数组元素的值--RAM里内存单元的可读可写属性不因对工作指针的限定而改变,而只会因对其本身的限定而改变。如const char c=‘A‘,c是RAM里一个内存单元(8字节)的名字,该内存单元的内容只可读,不可写。

(5)const char **p或者char const**p :涉及两个指针p和 *p。由于const修饰char ,对指针p没有任何限定,对指针*p进行了上面情况(2)的限定。

(6)char *const *p:涉及两个指针p和 *p。由于const修饰前面的char*,也就是对p所指向的内容*p进行了限定(也属于前面的情况(2))。而对*p来说,由于不能通过"*p=..."来进行另外赋值,因此属于前面的情况(3)的限定。

(7)char **const p : 涉及两个指针p和 *p,const修饰p,对p进行上面情况(3)的限定,而对*p,没有任何限定。

四、关于char **p 、const char **p的类型相容性问题

1。问题

char *p1;const *p2=p1;//合法

char **p1;const  char**p2=p1;//不合法,会有警告warning: initialization from incompatible pointer type

char **p1;char const**p2=p1;//不合法,会有警告warning: initialization from incompatible pointer type

char**p1;char*const*p2=p1;//合法

2。判断规则

明确const修饰的对象!对于指针p1,和p2,若要使得p2=p1成立,则可读做 :

“p1是指向X类型的指针,p2是指向“带有const限定”的X类型的指针 “。

char *p1;const *p2=p1;//合法:p1是指向(char)类型的指针,p2是指向“带有const限定"的(char)类型的指针。

char **p1;const  char**p2=p1;//不合法:p1是指向(char*)类型的指针,p2是指向 ((const char)*)类型的指针。

char **p1;char const**p2=p1;//不合法;与上等价。

char**p1;char*const*p2=p1;//合法:  p1是指向(char *)类型的指针,p2是指向“带有const限定"的(char*)类型的指针。

五、其他

1。 含有const的单层或双层指针的统一读法:

“p是一个指针,是一个[“带有const限定”的]指向[”带有const限定”的]X类型的指针”。

2。 定义时const修饰的对象是确定的,但不能在定义时加括号,不然就和定义时用“()”表示的函数类型相混淆了!因此定义时不能写(char *)const *p或者(const char) **p。

六、问题探讨

问题1
: 例如: const char wang[]={"wang"}; char *p; p=wang; 是错误的。 所以char *p中的P不能指向常变量。

在ubuntu 10.04(gcc 4.4.3)下做了如下测试:

 #include<stdio.h>
   int main()
   {
     const char wang[]={"wang"};
     char *p;
     p=wang;
     p[2]='c';
     printf("p is %s/n",p);
     return 0;
  }

编译 :

gcc -o test_const test_const.c

输出如下 :

test_const.c: In function ‘main’:

test_const.c:17: warning: assignment discards qualifiers from pointer target type

执行:

./test_const

p is wacg

结论: 根据本博文中第四点--相容性问题,将const型的wang赋值给p是不合法的。但编译器对其的处理只是警告,因此执行时通过p修改了只读区域的数据。这应该是该编译器处理不严所致后果,但是在GCC 4.8.1中会直接抛出致命错误: [Error] invalid conversion from ‘const char*‘ to ‘char*‘ [-fpermissive]

问题2:

在c语言里
// test.c
int main() {
   const char* s1 = "test";
   char *s2 = s1;
   s2 = "It's modified!";
   printf("%s/n",s1);
}
out: It's modified!;
这样也可以吗? 照我的理解岂不是const限定符在c语言里只是摆设一个?

回复:

(1)首先,以上代码编译时会出错warning: initialization discards qualifiers from pointer target type,因为char *s2 = s1和问题1提到的一样,不符合相容规则。

(2)输出结果是正确的,代码分析如下:

int main()
{
   const char* s1 = "test";
   // 在只读数据区(objdump -h test后的.rodata区)开辟5字节存储"test",并用s1指向首字符‘t’。
   char *s2 = s1;
   //  s2也指向只读数据区中“test”的首字符't'。
   s2 = "It's modified!";
   // 在只读数据区开辟15字节存储"It's modified!",并将s2由指向't'转而指向"It's modified!"的首字符'I'。
   printf("%s/n",s1);
   // 从s1所指的‘t’开始输出字符串"test"。
}

(3)总结:

提问者的误区在于,误以为s2 = "It‘s modified!"是对“test”所在区域的重新赋值,其实这里只是将“万能”工作指针s2指向另外一个新开辟的区域而已。比如若在char *s2 = s1后再执行s2[2]=‘a‘则是对“test”的区域进行了写操作,执行时会出现段错误。但这个段错误其实与const没有关系,因为“test”这块区域本身就是只读的。为了防止理解出错,凡事对于对指针的赋值(比如
s2 = "It‘s modified!" ),则将其读做:将s2指向“ It‘s modified! ”所在区域的首字符。

(4)额外收获:执行gcc -o test test.c后,“test”、“It‘s modified!”、"%s/n"都被作为字符串常量存储在二进制文件test的只读区 域 (.rodata)。事实上,一个程序从编译到运行,对变量空间分配的情况如下:

A。赋值了的全局变量或static变量=>放在可执行文件的.data段。

B。未赋值的全局变量或static变量=>放在可执行文件的.bss段。

C。代码中出现的字符串常量或加了const的A=>放在可执行文件的.rodata段。

D。一般的局部变量=>在可执行文件中不占空间,在该二进制文件作为进程在内存中运行时才为它分配栈空间。

E。代码中malloc或new出的变量=>在可执行文件中不占空间,在该二进制文件作为进程在内存中运行时才为它分配堆空间。

对const关键字的理解

目前在进行C语言补习时,发现很多的同学对于const这个关键字的理解存在很大的误解。现在总结下对这个关键字理解上的误区,希望在以后的编程中,能够灵活使用const这个关键字。

1、 const修饰的变量是常量还是变量

对于这个问题,很多同学认为const修饰的变量是不能改变,结果就误认为该变量变成了常量。那么对于const修饰的变量该如何理解那?

下面我们来看一个例子:

int main()
{
        char buf[4];
        const int a = 0;
        a = 10;
}

这个比较容易理解,编译器直接报错,原因在于“a = 10;”这句话,对const修饰的变量,后面进行赋值操作。这好像说明了const修饰的变量是不能被修改的,那究竟是不是那,那么下面我们把这个例子修改下:

#include<cstdio>

int main()
{
    char buf[4];
    const int a = 0;
    buf[4] = 97;
    printf("the a is %d/n",a);
}

其中最后一句printf的目的是看下变量a的值是否改变,根据const的理解,如果const修饰的是变量是不能被修改的话,那么a的值一定不会改变,肯定还是0。但是在实际运行的结果中,我们发现a的值已经变为97了。这说明const修饰的变量a,已经被我们程序修改了。

那综合这两个例子,我们来分析下,对于第二例子,修改的原因是buf[4]的赋值操作,我们知道buf[4]这个变量已经造成了buf这个数组变量的越界访问。buf数组的成员本身只有0,1,2,3,那么buf[4]访问的是谁那,根据局部变量的地址分配,可以知道buf[4]的地址和int a的地址是一样,那么buf[4]实际上就是访问了const int a;那么对buf[4]的修改,自然也修改了const int a的空间,这也是为什么我们在最后打印a的值的时候看到了97这个结果。

那么我们现在可以知道了,const修饰的变量是不具备不允许修改的特性的,那么对于第一个例子的现象我们又如何解释那。

第一个例子,错误是在程序编译的时候给出的,注意这里,这个时候并没有生成可执行文件,说明const修饰的变量可否修改是由编译器来帮我们保护了。而第二个例子里,变量的修改是在可执行程序执行的时候修改的,说明a还是一个变量。

综上所述,我们可以得出一个结论,那就是const修饰的变量,其实质是告诉程序员或编译器该变量为只读,如果程序员在程序中显示的修改一个只读变量,编译器会毫不留情的给出一个error。而对于由于像数组溢出,隐式修改等程序不规范书写造成的运行过程中的修改,编译器是无能为力的,也说明const修饰的变量仍然是具备变量属性的。

2、 被const修饰的变量,会被操作系统保护,防止修改

如果对于第一个问题,有了理解的话,那么这个问题,就非常容易知道答案了。]const修饰的变量是不会被操作系统保护的。其原因是操作系统只保护常量,而不会保护变量的读写。那么什么是常量?比如“hello world”这个字符串,常数1,2就是被称常量。

对于这个问题的另一种证明方法,可以看下面这个程序:

int main()
{
    const int a;
    char *buf = "hello world";
    printf("the &a is %p, the buf is %p/n",&a, buf);
    return 0;
}

可以发现buf保存的地址是在0x08048000这个地址附近的,而a的地址是在0xbf000000这个地址附近的,而0x08048000附近的地址在我们linux操作系统上是代码段。这也说明了常量和变量是存放在不同区域的,自然操作系统是会保护常量的。

如果我们知道这个道理后,再看下面的题目:

程序1

#include<stdio.h>
int main()
{
 	char *buf = "hello";
 	buf[0]='a';
 	printf("the buf is %s\n",buf);
 	return 0;
}

运行崩溃

程序2

#include<stdio.h>
int main()
{
	char s[6]="hello";
 	char *buf=s;
 	buf[0]='a';
 	printf("the buf is %s\n",buf);
 	return 0;
}

运行正常

思考下,这两个程序的运行结果为什么会出现这样的区别呢?

时间: 2024-10-12 10:37:29

const char*, char const*, char*const使用时的区别的相关文章

JS中,for和while在使用时的区别。

for用于可预测循环次数的情况,而while可在不确定循环次数时使用, while甚至可以不在循环指令上指定跳出循环的条件. so,正常使用的时候,应该优先考虑for循环,一般情况下for循环的效率都是最高的.

react-router与react-router-dom使用时的区别

1.React-router与React-router-dom的API对比 React-router:提供了router的核心api.如Router.Route.Switch等,但没有提供有关dom操作进行路由跳转的ap: React-router-dom:提供了BrowserRouter.Route.Link等api,可以通过dom操作触发事件控制路由.  2.React-router与React-router-dom的功能对比 React-router:实现了路由的核心功能 React-ro

char * p = &quot;abc&quot;与const char *p = &quot;abc&quot;

char * p = "abc"与const char *p = "abc"的区别是什么呢? 第一个语句会产生问题: warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings] 此时我们只需要改成第二个用法警告就消失了,这是为什么呢? 我们来分别理解以下两个语句,首先要清楚的是char * p语句定义的p是一个指针变量,假设我们用的是第一个语句,那么我们所表达的意思

const char* &amp;p 和 char* const &amp;p 区别

const char* &p 和 char* const &p 两种引用: 两者都是对一个对象的引用. 但是前者的"这个对象"是 const char*,一个指向 const char 的指针,注意!虽 然这个指针指向的char不可以改变,但这个指针本身的值是可以改变的,也就是说, 他可以被改变而指向另一个 const char 对象. 后者的"这个对象"则是char*, 一个指向char的指针.这个指针指向的东西是可以 改变的,但是这个指针本身是不

error: C2664: “zajiao::zajiao(const zajiao &amp;)”: 无法将参数 1 从“const char [12]”转换为“char *”

原本打算用一个字符串"ABCDEF12345"作为类zajiao的构造函数的参数,用来创建类zajiao的对象zajiao1. 1 zajiao zajiao1("ABCDEF12345"); 结果提示: F:\Users\denggelin\Documents\qtduojicheng\main.cpp:31: error: C2664: “zajiao::zajiao(const zajiao &)”: 无法将参数 1 从“const char [12]

Invalid arguments &#39; Candidates are: std::basic_ostream&lt;char,std::char_traits&lt;char&gt;&gt; &amp; write(const char *, int) &#39;

const char* p = "hello itcast"; cout.write(p,strlen(p)) << endl; cout.write(p,strlen(p) - 4) << endl; cout.write(p,strlen(p) - 4) << endl; 代码十分简单,通过对比vs,得知gcc调用的是ostream.tcc文件中的write函数write(const _CharT* __s, streamsize __n). v

char *s 和 char s[] 的区别小结

目前中有不少c的程序,在与项目新成员的交流中发现,普遍对于char *s1 和 char s2[] 认识有误区(认为无区别),导致有时出现“难以理解”的错误.一时也不能说得很明白,网上也搜了一下相关文章发现一些写的比较好的,综合了一下当教育资料备用. char *s1 = "hello";char s2[] = "hello"; [区别所在] char *s1 的s1,而指针是指向一块内存区域,它指向的内存区域的大小可以随时改变,而且当指针指向常量字符串时,它的内容

C 语言的 const 指针,指针的const有什么不同

Read it backwards (as driven by Clockwise/Spiral Rule)... int* - pointer to int int const * - pointer to const int int * const - const pointer to int int const * const - const pointer to const int Now the first const can be on either side of the type

char、signed char、unsigned char的区别

ANSI C 提供了3种字符类型,分别是char.signed char.unsigned char char相当于signed char或者unsigned char,但是这取决于编译器! 这三种字符类型都是按照1个字节存储的,可以保存256个不同的值. 不同的是取值范围signed char取值范围是 -128 到 127unsigned char 取值范围是 0 到 255 signed char的最高位为符号位,因此char能表示-128~127, unsigned char没有符号位,