1.表示字符串和字符串I/O
字符串是以空字符'\0'结尾的char类型数组,因为字符串是最有用、最重要的数据类型之一,C语言提供了大量处理字符串的函数。
与printf()函数相同,puts()函数也是属于stdio.h系列中的输入/输出函数,只不过puts是专门处理字符串的,并且自动在显示字符串的末尾加上换行符。
1.1字符串常量
用双引号括起来的内容称为字符串字面量,也叫做字符串常量。双引号中的内容编译器会自动加入末尾的空字符'\0',一起作为字符串存入内存中。如果要在字符串内部使用双引号,需要在待使用的双引号前面加一个反斜杠\。
用双引号括起来的内容被视为指向该字符串储存位置的指针,这类似与把数组名作为指向该数组位置的指针。
%p显示出一个地址,说明代表一个地址,*"strptr"表示该字符串所指向地址上存储的值,所以会显示首字符s。
1.2字符串数组和初始化
字符串可以看作是一个特殊的字符数组,因为字符串末尾有一个空字符,如果没有空字符,则是一个字符数组。字符串的初始化与数组初始化相同,唯一的区别就是内容上有一个空字符,如果未被使用的元素空间会被自动初始化为空字符'\0',像
已 |
被 |
使 |
用 |
的 |
空 |
间 |
\0 |
\0 |
\0 |
后面三个元素空间被初始化为了空字符。
字符串常量属于静态存储类别,这说明如果在函数中使用字符串常量,该字符串只会被存储一次,在整个程序的生命周期内存在,即使函数被调用多次。通常,字符串都作为可执行文件的一部分存储在数据段中,当程序载入内存时,也载入了程序中的字符串,字符串存储在静态存储区中,但是程序在开始运行时才会为该字符串数组分配内存,即此时才将字符串拷贝到数组中。此时字符串有两个副本,一个是在静态内存中的字符串常量,一个使存储在数组中的字符串。
可以看到字符串常量即使被printf()函数多次调用,它的存储地址也没有改变,而数组作为字符串的一个拷贝,它已经被存储到了其它地方。
1.3const、数组和指针
以上两个声明表明,ptr和str都是字符串的地址,但数组是字符串拷贝后的地址,ptr是在静态存储区的地址。总之,初始化数组把静态存储区的字符串拷贝到数组中,而初始化指针只能把字符串的地址拷贝给指针。
比起#define指令,const的用法更灵活,可以创建const数组、const指针和指向const的指针等等。
这里不能通过指向const的指针p来改变数组的值,但是可以用n来改变数组的值,因为数组n并未用const修饰,并且p能够递增指向别的位置,但是n++是不允许的,因为n作为数组首元素的地址,是一个常量。指向const的指针通常用于函数形参中,表明该函数不会使用指针改变数据,如void fun(const void *, const void *);
。
同时指向const的指针可以赋值为const数据或非const数据的地址,而普通指针只能赋值为非const数据的地址,这也很好理解,因为,如果const数据能被普通指针修改,那将引起很严重的问题。
const还有其它的用法,例如,可以声明初始化为一个不能指向别的位置的指针,例如
此时p就是一个指向int类型的const修饰的指针,不能再指向别的位置,与此同时,指向的数据也是const修饰的,不能被指针p修改。
数组表示法和指针表示法的区别,数组名是常量,而指针是变量,所以只有指针才能进行递增或递减操作,其它时候,都能通过数组名或指针遍历字符串数组。
有些编译器可能允许这样做,但是目前的C标准,对于这样的行为是未定义的,所以这样的语句尽量不要写,否则可能造成一些未知的严重后果,例如可能造成内存访问错误。原因前面提过,字符串常量使用内存中的一个副本来表示所有完全相同的字符串字面量。因此建议再把指针初始化为字符串字面量时使用const修饰符,或者可以使用非const数组来初始化字符串字面量,因为数组获得的是原始字符的副本。所以打算修改字符串时,就不要使用指针指向字符串字面量。
1.4字符串数组
mytalents数组是一个内含5个指针的数组,总共占用40字节(因为char *是8字节)。而yourtalents是一个内含5个数组的数组,每个数组内含40个char类型的值,总共占用200字节。mytalents中的指针指向初始化时所用的字符串字面量的位置,这些字符串字面量被存储在静态内存中;而yourtalents中的数组则存储着字符串字面量的副本,所以每个字符串都被存储了两次。同时,yourtalents中的每个元素大小必须相等,而且必须至少是能存储最长字符串的大小,所以,为字符串数组分配内存的使用率较低。
综上所述,如果要用数组表示一系列待显示的字符串,请使用指针数组,因为指针数组的效率更高。