getchar与putchar缓冲区以及字符串数组、指针
getchar
与putchar
缓冲区
有下面的语句段:
while ((s = getchar()) != '\n'){
putchar(s);
putchar("\n");
}
这个while循环是怎么工作的呢?
首先getchar
在数据缓冲区里读数据进来,这个读取数据是一个个读的,循环依次判断读进来的字符是不是为换行符,是的话就跳出循环,不是就打印出该字符。其实在getchar
读取字符之前,通过键盘输入的字符已经通过stdin
流全部存入了缓冲区中,getchar
只在用户输入了换行符时才会去读取缓冲区里的数据。
C字符串数组与指针
在C中字符串实质上是最后一个元素为空字符的char
数组,而我们知道数组与指针是有关联的。声明一个字符串有以下两种方式:
char *string1 = "foo";
//通过指针声明,string1其实是指向"foo"这个字符串第一个char字符'f'的地址
char string2[4] = "foo";
//通过char数组生明字符串,string2是一个数组
这两种声明方式有一点不同,就是数组名string2
是个常量,而指针名string1
是一个变量。数组形式的string[4]
是计算机内存中分配的用于存储四个字符'f','o','o','o','\0'
,string2
既是数组名,也是首个字符string[0]
的地址,string2
的值不能更改,可以通过string2+1
来访问第二个字符'o'
,但不能这样用:++string2
,C中规定++
自增符号只能用于变量,string2
这里显然是个常量,它的值是该数组首个元素的地址。对于string1
形式,C Primer Plus中这样说:
指针形式(也就是我们这里的
string1
)会在静态存储区为字符串预留4个元素的空间(最后一个存储空间用来放'\0'
)。一旦程序开始执行,还要为指针变量string1
另外预留一个存储位置,以在该指针变量中存储字符串地址。这个变量初始时指向字符串的第一个字符,但是它的值是可以改变的。因此,可以对它使用增量运算符(例++string1
将指向第二个字符'o'
)。总之数组形式的字符串初始化是从静态存储区把一个字符串赋值给数组,而指针初始化只是复制字符串的地址。
这里我们就很清楚了两种形式的不同。一般推荐用数组来定义字符串,因为用指针形式定义字符串时,若是修改了指针指向的字符串某个字符可能会导致所有使用该地址字符的值发生变化,这和编译器的实现方式有关。
在访问字符串,例如puts
时,不管是数组形式还是指针形式,会一直打印字符知道碰到空字符才结束。
字符串数组
考虑到一下形式定义:
char (*list)[2] ={"a","b"};//1
const char *multiple_strings[3] = {//2
"I love you",
"You are beautiful",
"You makes me happy"};
你会发现1处会报警告:Incompatible pointer types initializing 'char (*)[2]' with an expression of type 'char [2]'; take the address with &
因为此处声明的list是一个指向由两个字符构成的元素的元素的地址,而"a"
是一个字符串,并不是个字符,因此我们得把"a"
改成'a'
。其实这样也是错误的,我们声明list是个指针,指向一个二维数组的第一个元素,那么就不要用一整个二维数组类型去初始化它,最好这样:
char (*list)[2] = {'a','b'};
char test[2][2] = {{'a','b'},{'b','c'}};
list = test;
这样不会有任何警告。
再看注释2处什么意思呢?这其实定义了一个字符串数组,但是这是一个一位数组,而不是二维数组(我们知道二维数组指针形式声明并不是这样的),数组里面的每个元素都是一个char类型元素的地址,每个元素都是对应字符串第一个字符的地址,也就有下列关系成立:
*multiple_strings[0] == 'I'; *multiple_strings == 'Y'
。对指针进行数组式操作在这里也是成立的:
multiple_strings[1][2]
就对应的就是第二个字符串You
中的'u'
。
关于这部分的详细知识可以参见C Primer Plus的第十一章关于字符串数组的部分。
总结: