本文共 2911 字,大约阅读时间需要 9 分钟。
对一些C语言的知识进行总结,重点是最后关于字符数组、字符指针、字符串的讨论。
1.变量的初始化
简单说就是,静态变量初始化为0,自动变量不初始化。
什么是不初始化呢?就是说,可能是任意值。
注意:
(1)对于非指针变量,当你声明了一个变量时,你就获得了掌控权,系统保证了除非你自己实现,否则这个变量不会用作它用。
所以虽然这个变量里面的值是任意的,但是你可以随意修改这个值而不会有其他影响。
(2)对于指针变量,当你声明了一个变量,指针变量的值不确定,你随意修改指针变量的值不会有其他影响,
但是如果你直接通过指向运算,改变一个不确定地址指向的内容,就会存在问题。因为系统并不保证一个随机的地址会指向哪里,不确定那里的内容在被谁使用,
所以你随意修改造成的后果也是不确定的。
2.解读复杂声明
其实就是一个口诀,按照普通的优先级(这里需要注意的优先级是() > [] > *)去解读那个表达式即可,看什么与名字先结合,它就首先是一个什么。
比如,
int *p; 这里*与p先结合,所以p是一个指针,指向int
int *p(); 这里()优先级高于*,所以p是一个函数,函数返回值是int *
int *p[]; 这里[]优先级高于*,所以p是一个数组,数组元素是int*
int (*p)[]; 这里因为有括号在,所以p是一个指针,指向一个数组,数组元素是int
更复杂的表达式同理,重点是记住优先级() > [] > *
3.const与指针
直接上3个例子
(1)const int* a
(2)int const* a
(3)int *const a
其实有了2的道理,这里还是很容易搞明白的,首先(1)(2)中,a是一个指针,指向const int
这里注意两点
①const int 和 int const是等价的
②虽然看上去是a会指向一个const int,但是这只是表明不能通过此指针去间接改变其指向的元素,该元素本身并不一定是一个const类型的元素
(3)的话,表明a本身就是一个const的量,所以a是一个指针,其自身的值不能改变,指向a指向的是一个int值。
看几个栗子:
int x = 5;
int y = 6;
const int* a1 = &x;
int const* a2 = &x;
int *const a3 = &x;
*a1 = 4; //错误,因为不能通过a1、a2去改变x的值
*a3 = 4; //正确
a1 = &y; //正确
a3 = &y; //错误,因为a3是一个const的指针,不能改变a3本身的值
4.字符指针、字符数组、字符串
(1)字符指针
①最没什么可以说的,就是一个普通指针,指向的类型是字符。自动变量,未初始化时,内容不确定。
②如果直接用cout输出字符指针,会将其指向的内容当成一个字符串,遇到字符串结束符才结束。
③sizeof一个字符指针,和其他所有的指针一样,在32位机器上,长度为4 bytes
(2)字符数组
其实重点需要说明的是,字符数组的数组名
比如char int[] = "lalala";
①可以将a看作一个常量指针
看作的意思是说,具有常量指针的有些特性,但是又有不一样的地方。
一样的地方包括,可以进行*a,*(a+2)这样的指针操作,并且a是const的,不能对其再赋值。
不一样的地方包括,
A。sizeof(a),表示的是数组的字符长度,而不是一个指针的长度,所以这里sizeof(a) == 6
B。这个指针指向的地方是可控的,被程序员所掌控的数组空间,虽然里面的值不确定,但是可以保证不被其它程序所使用。相当于这个指针是被初始化了的。
②声明数组参数
结论是,当把一个字符数组声明为函数参数时,其实只是把字符指针作为了函数参数。
以下两步可以证明
A。定义test(char* str){} 和 test(char str[]){}
这里编译出错,出错原因是重复定义,表明这两个函数不构成重载,作为函数的参数的char* str和char str[],这两个str是完全等价的。
前面已经说过了,字符数组的数组名并不完全与字符指针等价,但是作为函数参数时它们等价了,那么到底是作为它们的哪一个处理的呢?
B。test(char str[]) {cout << sizeof(str) << endl;}
char str[] = "lalala";
test(str);
运行,打印出的结果是4,而不是6,可以看出是讲char str[]当做char *str处理了。
(3)字符串
C语言并没有显式的字符串类型,字符串要么是①字符串常量,要么②存储于字符数组中
字符串常量,比如“lalala”,是一个神奇的东西,我们就来说一说字符串常量。
当一个字符串常量出现在表达式中时,它的值是个指针常量。
编译器把这些指定的字符的一份拷贝存储在内存的某个位置,并存储一个指向第一个字符的指针。
①以下的表达式虽然会吓人一跳,但是还是会成立的
"xyz" + 1;
*"xyz";
"xyz"[2];
这些你把字符串常量认为是一个指针常量,就都能解释了。
②这个指针常量,到底是顶层还是底层呢?
我想,应该既是顶层,也是底层。//这个结论我现在认为有误,具体见后面的补充
顶层证明:"lalala" = "nonono"; //编译错误
底层证明:"lalala"[2] = 'x'; //编译错误
③特殊初始化
看两个例子
char* str1 = "lalala";
char str2[] = "lalala";
看上去一样,其实不一样。
第一个就是一个指针的拷贝,将在某个内存区域的指针,直接赋值给str1
第二个是还要将值拷贝,将某个内存区间的值,拷贝到数组所在内存。并且自动添加表示字符串结束的空字符。
因为*str1 = 'n'; //运行出错
*str2 = 'n'; //ok的
2016/07/29补充:
1.字符串常量的类型
先直接上结论,是一个指针常量,是一个顶层的指针常量,并不是底层,但是其指向的区域是一个只读的区域。
(1)验证顶层
"abc" = "aaa";编译错误
(2)验证非底层
char* c = "lalala";没有错误
int x = 5;
const int* px1 = &5;
int* px2 = px1;编译错误
出现将底层的常量指针赋值给非常量指针时,会发生编译错误,才能证明被赋值的是底层常量指针。
但是char* c = "lalala";没有错误,说明字符串常量不是底层常量指针。
(3)验证指向的区域是只读区域
"abc'[2] = 'k';编译错误
char* c = "lalala";
*(c+1) = 'x';运行时错误
补充:
再次强调关于底层常量指针的认识。
并不是说这个指针一定指向一个常量,而是承诺说不能通过这个指针去修改被指向的内存。
这样就能理解字符串常量为什么是一个顶层指针而不是一个底层指针,单却指向一个只读的内存区域。
转载地址:http://hniei.baihongyu.com/