C语言中没有特定的字符串类型,我们通常是将字符串放在一个字符数组中,字符数组用法很简单我们还是来你看个例子吧。

#include <stdio.h>
#include <string.h>

int main()
{
char str[] = "hello world!\n";
int len = strlen(str), i;

//直接输出字符串
printf("%s", str);

//每次输出一个字符
for(i=0; i<len; i++)
{
printf("%c", str[i]);
}
return 0;
}

运行结果:

ouxiaolong@ubuntu:~/share$ gcc main.c -o main
ouxiaolong@ubuntu:~/share$ ./main
hello world!
hello world!

字符数组归根结底还是一个数组,上节讲到的关于指针和数组的规则同样也适用于字符数组。更改上面的代码,使用指针的方式来输出字符串:

#include <stdio.h>
#include <string.h>

int main()
{
char str[] = "hello world";
char *p = str;
int len = strlen(str), i;
//直接打印
printf("%s\n", p);

//使用*(p+i)
for(i=0; i<len; i++)
{
printf("%c", *(p+i));
}
printf("\n");

//使用p[i]
for(i=0; i<len; i++)
{
printf("%c", p[i]);
}
printf("\n");

//使用*(str+i)
for(i=0; i<len; i++)
{
printf("%c", *(str+i));
}
printf("\n");
return 0;
}

运行结果:

ouxiaolong@ubuntu:~/share$ gcc main.c -o main
ouxiaolong@ubuntu:~/share$ ./main
hello world
hello world
hello world
hello world

除了字符数组,C语言还支持另外一种表示字符串的方法,就是直接使用一个指针指向字符串,例如:

char *str = "hello world";

或者:

char *str;
str = "hello world";

字符串中的所有字符在内存中是连续排列的,str 指向的是字符串的第 0 个字符;我们通常将第 0 个字符的地址称为字符串的首地址。字符串中每个字符的类型都是char,所以 str 的类型也必须是char *。

下面的例子演示了如何输出这种字符串:

#include <stdio.h>
#include <string.h>

int main()
{
char *str = "hello world";
int len = strlen(str), i;

//直接输出字符串
printf("%s\n", str);

//使用*(str+i)
for(i=0; i<len; i++)
{
printf("%c", *(str+i));
}
printf("\n");

//使用str[i]
for(i=0; i<len; i++)
{
printf("%c", str[i]);
}
printf("\n");
return 0;
}

运行结果:

ouxiaolong@ubuntu:~/share$ gcc main.c -o main
ouxiaolong@ubuntu:~/share$ ./main
hello world
hello world
hello world

这一切看起来和字符数组是多么地相似,它们都可以使用%s输出整个字符串,都可以使用*或[ ]获取单个字符,这两种表示字符串的方式是不是就没有区别了呢?

有!它们最根本的区别是在内存中的存储区域不一样,字符数组存储在全局数据区或栈区,字符指针的字符串存储在常量区。全局数据区和栈区的字符串(也包括其他数据)有读取和写入的权限,而常量区的字符串(也包括其他数据)只有读取权限,没有写入权限。

内存权限的不同导致的一个明显结果就是,字符数组在定义后可以读取和修改每个字符,而对于第二种形式的字符串,一旦被定义后就只能读取不能修改,任何对它的赋值都是错误的。

为了进一步说明字符指针,我们接下来还要对其深入剖析,字符指针的字符串称为字符串常量,意思很明显,常量只能读取不能写入。

首先来看看字符数组的例子。

#include <stdio.h>

int main()
{
char str[] = "hello world\n";
printf("%s", str);

str[0] = 'a';
str[1] = 'b';
str[2] = 'c';

printf("%s", str);

return 0;
}

结果如下:

ouxiaolong@ubuntu:~/share$ gcc main.c -o main
ouxiaolong@ubuntu:~/share$ ./main
hello world
abclo world

再来看看字符指针的例子,看看有何不同。

#include <stdio.h>
int main()
{
char *str = "hello world\n";
str = "I love C!"; //正确
str[3] = 'P'; //错误

printf("%s",str);

return 0;
}

结果如下:

ouxiaolong@ubuntu:~/share$ gcc main.c -o main
ouxiaolong@ubuntu:~/share$ ./main
Segmentation fault (core dumped)

这段代码能够正常编译和链接,但在运行时会出现段错误(Segment Fault)或者写入位置错误。第6行代码是正确的,可以更改指针变量本身的指向;第5行代码是错误的,不能修改字符串中的字符。

在编程过程中如果只涉及到对字符串的读取,那么字符数组和字符串常量都能够满足要求;如果有写入(修改)操作,那么只能使用字符数组,不能使用字符串常量。

获取用户输入的字符串就是一个典型的写入操作,只能使用字符数组,不能使用字符串常量,请看下面的代码:

#include <stdio.h>
#include <string.h>

int main()
{
char str[30];
fgets(str,30,stdin);
str[strlen(str)-1] = '\0';
printf("%s\n", str);

return 0;
}

运行结果:

ouxiaolong@ubuntu:~/share$ gcc main.c -o main
ouxiaolong@ubuntu:~/share$ ./main
hello world
hello world

【注1】为何不使用gets函数输入请参看:

【注2】为何不使用scanf函数请参看:

总结

数组形式:

char str[] = “hello world"; //栈(局部)

字符指针形式:

char *str = “hello world"; //文字常量区

数组形式与字符指针形式都是字符串的表示形式,但是这两种表示形式大不相同。

下面以数字形式字符串char str[] = “hello world"; 与指针形式字符串char *str = “hello world”;为例:

1、储存方式

(1)字符数组由若干元素组成,每个元素存放一个字符,

(2)而字符指针变量只存放字符串的首地址,不是整个字符串。

2、存储位置

(1)数组是在内存中开辟了一段空间存放字符串;

(2)而字符指针是在文字常量区开辟了一段空间存放字符串,将字符串的首地址付给指针变量str。

3、赋值方式

对与数组,下面的赋值方式是错误的:

char str[10];
str="hello"

而对字符指针变量,可以采用下面方法赋值:

char *a;
="hello"

4、可否被修改

(1)指针变量指向的字符串内容不能被修改,但指针变量的值(即存放的地址或者指向)是可以被修改的;

例一:指针变量指向的字符串内容不能被修改

char *p = “hello”; //字符指针指向字符串常量

p = ‘a’; //错误,常量不能被修改,即指针变量指向的字符串内容不能被修改
说明:定义一个字符指针指向字符串常量“hello”,修改指针变量指向的字符串的内容,即
​p = ‘a’,发生错误,指针变量指向字符串常量,而常量字符串存在文字常量区,这段空间中的内容为只读内容,不能被修改,即指针变量指向的字符串内容不能被修改。

例二:指针变量的值可以被修改

char *p = "hello"; //字符指针指向字符串常量
char ch = 'a';
p = &ch; //指针变量指向可以改变

说明:定义一个字符指针指向字符串常量“hello”,同时定义一个字符变量ch,改变指针变量的指向,即让p指向字符变量ch,这样是可以的,即指针变量的指向是可以改变的。

(2)字符串数组内容可以被修改,但字符串数组名所代表的字符串首地址不能被修改

例子:定义了一个数组buf,编译器在编译时为它分配内存单元,有确定的地址,此例子中为0X0034FDCC,给buf赋不同的值,字符串数组数组名所代表的字符串首地址没有改变,一直为0X0034FDCC。

5、初始化

定义了一个数组,在编译时为他分配内存单元,他有确定的地址;而在定义一个字符指针变量时,最好将其初始化,否则指针变量的值会指向一个不确定的内存段,将会破坏程序,以下方式是允许的:

char str[10];
scanf("%s", str); //或使用字符串拷贝函数进行拷贝赋值

以下方式不推荐,是很危险的:

char *p; //指针变量未初始化,指向一个不确定的内存段
scanf("%s", p);

以下方式是推荐使用的:

char *p = NULL;
p = (char *)malloc(10);
scanf("%s", p); //或使用字符串拷贝函数进行拷贝赋值