通过指针引用字符串详解,以及字符指针变量和字符数组的比较

您所在的位置:网站首页 符号常量举例 通过指针引用字符串详解,以及字符指针变量和字符数组的比较

通过指针引用字符串详解,以及字符指针变量和字符数组的比较

2023-03-15 22:10| 来源: 网络整理| 查看: 265

在平常的案例中已大量地使用了字符串,如在 printf函数中输出一个字符串。这些字符串都是以直接形式 (字面形式) 给出的,在一对双引号中包含若干个合法的字符。在本节中将介绍使用字符串的更加灵活方便的方法:通过指针引用字符串。

目录

一、字符串的引用方式

1.1引入

二、字符指针作函数参数

2.1举例说明

2.2程序改进

三、使用字符指针变量和字符数组的比较

一、字符串的引用方式 1.1引入

在 C 程序中,字符串是存放在字符数组中的。想引用一个字符串,可以用以下两种方法。

(1)用字符数组存放一个字符串,可以通过数组名和下标引用字符串中一个字符,也可以通过数组名和格式声明 “ %s ” 输出该字符串。

举例:定义一个字符数组,在其中存放字符串 " I love China! " ,输出该字符串和第8个字符。

#include int main() { char string[] = "1 love China!"; //定义字符数组string printf("%s\n", string); //用%s格式声明输出string,可以输出整个字符串 printf("%c\n", string[7]); //用%c格式输出一个字符数组元素 return 0; }

运行结果:

程序分析:

在定义字符数组 string 时未指定长度,由于对它初始化,因此它的长度是确定的,长度应为14,其中 13 个字节存放 " l love China! " 13个字符,最后一个字节存放字符串结束符 '\0'。数组名 string  代表字符数组首元素的地址。题目要求输出该字符串第 8 个字符,由于数组元素的序号从 0 起算,所以应当输出 string[7],它代表数组中序号 7 的元素的值(它的值是字母 C )。实际上 string[7] 就是 *(string+7),string+7 是一个地址,它指向字符 “ C ”。

(2)用字符指针变量指向一个字符串常量,通过字符指针变量引用字符串常量。

举例:通过字符指针变量输出一个字符串。

#include int main() { char* string = "1 love China!"; //定义指针变量string printf("%s\n", string); //用%s格式输出整个字符串 printf("%c\n", *(string + 7)); //用%c格式输出一个元素 return 0; }

运行结果:

程序分析:

在程序中没有定义字符数组,只定义了一个 char* 型的指针变量(字符指针变量) string,用字符串常量 " I love China! " 对它初始化。C 语言对字符串常量是按字符数组处理的,在内存中开辟了一个字符数组用来存放该字符串常量,但是这个字符数组是没有名字的,因此不能通过数组名来引用,只能通过指针变量来引用。

对字符指针变量 string 初始化,实际上是把字符串第 1 个元素的地址(即存放字符串的字符数组的首元素地址)赋给指针变量 string,使 string 指向字符串的第 1 个字符,由于字符串常量 " I love China! " 已由系统分配在内存中连续的 14 个字节中,因此,string 就指向了该字符串的第一个字符。在不致引起误解的情况下,为了简便,有时也可说 string 指向字符串 " I love China! " ,但应当理解为 “ 指向字符串的第 1 个字符 ”。

注意:

string 被定义为一个指针变量,基类型为字符型。请注意它只能指向一个字符类型数据,而不能同时指向多个字符数据,更不是把 " I love China! " 这些字符存放到 string 中(指针变量只能存放地址),也不是把字符串赋给 *string。只是把 " I love China! " 的第 1 个字符的地址赋给指针变量。

%s 是输出字符串时所用的格式符,在输出项中给出字符指针变量名 string,则系统会输出 string 所指向的字符串第 1 个字符,然后自动使 string 加 1,使之指向下一个字符,再输出该字符……如此直到遇到字符串结束标志 '\0' 为止。在内存中,字符串的最后被自动加了一个 '\0' ,因此在输出时能确定输出的字符到何时结束。

说明:

通过字符数组名或字符指针变量可以输出一个字符串,而对一个数值型数组,是不能企图用数组名输出它的全部元素的。

例如:

int a[10]; ... printf("%d\n",a);

是不行的,它输出的是数组首元素的地址。对于数值型数组的元素值只能逐个输出。

举例:将字符串 a 复制为字符串 b,然后输出字符串b。

#include int main() { char a[] = "I am a student.", b[20];//定义字符数组 int i; for (i = 0; *(a + i) != '\0'; i++) *(b + i) = *(a + i); //将a[i的值赋给b[i *(b + i) = '\0'; //在b数组的有效字符之后加'\0' printf("string a is:%s\n", a); //输出a数组中全部有效字符 printf("string b is:"); for (i = 0; b[i] != '\0'; i++) printf("%c", b[i]); //逐个输出b数组中全部有效字符 printf("\n"); return 0; }

运行结果:

程序分析:

程序中 a 和 b 都定义为字符数组,今通过地址访问其数组元素。在 for 语句中,先检查 a[i] 是否为 '\0' ( a[i] 是以 *(a+i) 形式表示的)。如果不等于 '\0',表示字符串尚未处理完,就将 a[i] 的值赋给b[i],即复制一个字符。在 for 循环中将 a 串中的有效字符全部复制给了 b 数组。最后还应将 '\0' 复制过去,作为字符串结束标志。

在第 2 个 for 循环中输出 b 数组中的元素,在 printf 函数中用下标法表示一个数组元素(即一个字符)。也可以用输出 a 数组的方法输出b数组。用以下一行代替程序的10-13行。

printf("string b is:%s\n”,b);

程序中用逐个字符输出的方法只是为了表示可以用不同的方法输出字符串。

也可以用另一种方法:用指针变量访问字符串。通过改变指针变量的值使它指向字符串中的不同字符。

举例:用指针变量来处理问题。

#include int main() { char a[] = "I am a boy.", b[20], * pl, * p2; pl = a; p2 = b; //pl, p2分别指向a数组和b数组中的第一个元素 for (; *pl != '\0'; pl++, p2++) //pl,p2每次自加1 *p2 = *pl; //将pl所指向的元素的值赋给p2所指向的元素 *p2 = '\0'; //在复制完全部有效字符后加'\0' printf("string a is:%s\n", a); //输出a数组中的字符 printf("string b is:%s\n", b); //输出b数组中的字符 return 0; }

运行结果:

程序分析:

p1 和 p2 是指向字符型数据的指针变量。先使 p1 和 p2 分别指向字符串 a 和 b 的第1个字符。*p1 最初的值是字母 'I'。赋值语句 “ *p2 = *p1; ” 的作用是将字符 'I' ( a 串中第 1 个字符)赋给 p2 所指向的元素,即 b[0]。然后 p1 和 p2 分别加 1,分别指向其下面的一个元素,直到 *p1 的值为 '\0' 止。

二、字符指针作函数参数

如果想把一个字符串从一个函数 “传递” 到另一个函数,可以用地址传递的办法,即用字符数组名作参数,也可以用字符指针变量作参数。在被调用的函数中可以改变字符串的内容,在主调函数中可以引用改变后的字符串。

2.1举例说明

举例:用函数调用实现字符串的复制。

(1)用字符数组名作为函数参数

#include int main() { void copy_string(char from[], char to[]); char a[] = "I am a teacher."; char b[] = "You are a student."; printf("string a=%s\nstring b=%s\n", a, b); printf("\ncopy string a to string b:\n"); copy_string(a, b); //用字符数组名作为函数实参 printf("string a = % s\nstring b = % s\n", a, b); return 0; } void copy_string(char from[], char to[]) //形参为字符数组 { int i = 0; while (from[i] != '\0') { to[i] = from[i]; i++; } to[i] = '\0'; }

运行结果:

程序分析:

a 和 b 是字符数组。copy_string() 函数的作用是将 from[i] 赋给 to[i],直到 from[i] 的值等于 '\0' 为止。在调用 copy_string() 函数时,将 a 和 b 第 1 个字符的地址分别传递给形参数组名 from 和  to 。因此 from[i] 和 a[i] 是同一个单元,to[i] 和 b[i] 是同一个单元。程序执行完以后,由于 b 数组原来的长度大于 a 数组,因此在将 a 数组复制到 b 数组后,未能全部覆盖 b 数组原有内容。b 数组最后 3 个元素仍保留原状。在输出 b 时由于按 %s (字符串)输出,遇 '\0' 即告结束,因此第一个 '\0' 后的字符不输出。如果用 %c 逐个字符输出是可以输出后面这些字符的。

(2)用字符型指针变量作实参

#include int main() { void copy_string(char from[], char to[]); //函数声明 char a[] = "I am a teacher."; //定义字符数组a并初始化 char b[] = "You are a student."; //定义字符数组b并初始化 char* from = a, * to = b; //from指向a数组首元素,to指向b数组首元素 printf("string a=%s\nstring b=%s\n", a, b); printf("\ncopy string a to string b:\n"); copy_string(from, to); //实参为字符指针变量 printf("string a=%s\nstring b=%s\n", a, b); return 0; } void copy_string(char from[], char to[]) //形参为字符数组 { int i = 0; while (from[i] != '\0') { to[i] = from[i]; i++; } to[i] = '\0'; }

运行结果:

程序分析:

指针变量 from 的值是 a 数组首元素的地址,指针变量 to 的值是 b 数组首元素的地址。它们作为实参,把 a 数组首元素的地址和 b 数组首元素的地址传递给形参数组名 from 和 to (它们实质上也是指针变量)。

(3)用字符指针变量作形参和实参

#include int main() { void copy_string(char* from, char* to); char* a = "I am a teacher. "; //a是 char *型指针变量 char b[] = "You are a student. "; //b是字符数组 char* p = b; //使指针变量p指向b数组首元素 printf("string a=%s\nstring b=%s\n", a, b); //输出a串和b串 printf("\ncopy string a to string b : \n"); copy_string(a, p); //调用copy_string函数,实参为指针变量 printf("string a=%s\nstring b=%s\n", a, b);//输出改变后的a串和b串 return 0; } void copy_string(char* from, char* to) //定义函数,形参为字符指针变量 { for (; *from != '\0'; from++, to++) { *to = *from; } *to = '\0'; }

运行结果:

程序分析:

形参改用 char* 型变量(即字符指针变量)。在程序(1)和(2)中 copy_string 函数的形参用字符数组名,其实编译系统是把字符数组名按指针变量处理的,只是表示形式不同。copy_string 函数中不是用下标法引用数组元素,而是通过移动指针变量的指向,找到并引用数组元素。

main 函数中的 a 是字符指针变量,指向字符串 "I am a teacher." 的首字符。b 是字符数组,在其中存放了字符串 "You are a student." 。p 是字符指针变量,它的值是 b 数组第一个元素的地址,因此也指向字符串 "You are a student.” 的首字符。copy_string 函数的形参 from 和 to 是字符指针变量。在调用 copy_string 时,将数组 a 首元素的地址传给 from,把指针变量 p 的值(即数组 b 首元素的地址)传给 to。因此 from 指向 a 串的第一个字符 a[0],to 指向 b[0]。在 for 循环中,先检查 from 当前所指向的字符是否为' \0',如果不是,表示需要复制此字符,就执行 “ *to = *from ”,每次将 *from 的值赋给 *to,第 1 次就是将 a 串中第 1 个字符赋给 b 数组的第 1 个字符。每次循环中都执行 “from++” 和 “to++”,使 from 和 to 分别指向 a 串和 b 数组的下一个元素。下次再执行 “ *to = *from ” 时,就将 a 串中第 2 个字符赋给b[1]……最后将 '\0' 赋给 *to,注意此时 to 指向哪个单元。

2.2程序改进

对 copy_string 函数还可以改写得更精练一些,可以作以下一些改动:

(1)将 copy_string 函数改写为

void copy_string(char* from, char* to) { while ((*to = *from) != '\0') { to++; from++; } }

在本程序中将 “ *to = *from ” 的操作放在 while 语句括号内的表达式中,而且把赋值运算和判断是否为 '\0' 的运算放在一个表达式中,先赋值后判断。在循环体中使 to 和 form 增值,指向下一个元素……直到 *from 的值为 '\0' 为止。

(2)copy_string 函数的函数体还可以改为

{ while ((*to++ = *from++) != '\0'); }

把上面程序的 to++和 from++ 运算与 *to = *from合并,它的执行过程是,先将 *from 赋给 *to,然后使 to 和 from 增值。显然这又简化了。

(3)copy_string 函数的函数体还可以改为

{ while (*from!='\0') { *to++ = *from++; } *to != '\0'; }

当 *from 不等于 '\0' 时,将 *from 赋给 *to,然后使 to 和 from 增值。

(4)由于字符可以用其ASCII码来代替

例如,“ ch=' a' ”可用 “ ch= 97 ”代替,“ while(ch!='a') ” 可以用 “ while(ch!=97) ”代替。因此,“ while(*from!='\0') ” 可以用 “ while(*from!=0) ” 代替( '\0' 的 ASCII 代码为 0 )。而关系表达式 “ *from!=0 ” 又可简化为 “ *from ”,这是因为若 *from 的值不等于0,则表达式 “ *from ” 为真,同时 “ *from!=0 ” 也为真。因此 “ while(*from!=0) ” 和 “ while(*from) ”是等价的。

所以函数体可简化为

{ while (*from) { *to++ = *from++; } *to != '\0'; }

(5)上面的 while 语句还可以进一步简化为

while (*to++ = *from++);

它与下面的语句等价

while ((*to++ = *from++) != '\0');

(6)函数体中也可以改为只用一个 for 语句

for (; (*to++ = *from++) != '\0'; );

for (; *to++ = *from++; );

(7)也可以用字符数组名做函数形参,在函数中另定义两个指针变量p1,p2。函数 copy_string 可写为

void copy_string(char from[], char to[]) { char* pl, * p2; pl = from; p2 = to; while ((*p2++ = *pl++) != '\0'); }

以上各种用法,使用十分灵活,程序精练,比较专业,初学者看起来不太习惯,觉得含义不直观。初学者要很快地写出它们可能会有些困难,也容易出错。但应能看懂以上的用法。在对C熟练后,以上形式的使用是比较多的。

归纳起来,用字符指针作为函数参数时,实参与形参的类型有以下几种对应关系

实参形参字符数组名字符数组名字符数组名字符指针变量字符指针变量字符指针变量字符指针变量字符数组名 三、使用字符指针变量和字符数组的比较

用字符数组和字符指针变量都能实现字符串的存储和运算,但它们二者之间是有区别的,不应混为一谈,主要有以下几点。

(1)字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址(字符串第 1 个字符的地址),绝不是将字符串放到字符指针变量中。

(2)赋值方式。可以对字符指针变量赋值,但不能对数组名赋值。

(3)初始化含义。数组可以在定义时对各元素赋初值,但不能用赋值语句对字符数组中全部元素整体赋值。

char str[14] = "1 love China!";

不等价于

char str[14]; str[] = "1 love China!"; //把字符串赋给数组中的各元素,错误

(4)存储单元的内容。编译时为字符数组分配若干存储单元,以存放各元素的值,而对字符指针变量,只分配一个存储单元( Visual C++为指针变量分配 4 个字节)。

如果定义了字符数组,但未对它赋值,这时数组中的元素的值是不可预料的。可以引用(如输出)这些值,结果显然是无意义的,但不会造成严重的后果,容易发现和改正。

(5)指针变量的值是可以改变的,而字符数组名代表一个固定的值(数组首元素的地址),不能改变。

(6)字符数组中各元素的值是可以改变的(可以对它们再赋值),但字符指针变量指向的字符串常量中的内容是不可以被取代的(不能对它们再赋值)。

(7)引用数组元素。对字符数组可以用下标法(用数组名和下标)引用一个数组元素(如 a[5] ),也可以用地址法 ( 如 *(a+5) ) 引用数组元素 a[5]。如果定义了字符指针变量 p,并使它指向数组 a 的首元素,则可以用指针变量带下标的形式引用数组元素 ( 如 p[5] ),同样,可以用地址法(如 *(p+5) )引用数组元素 a[5]。

(8)用指针变量指向一个格式字符串,可以用它代替 printf 函数中的格式字符串。

char* format; format = "a=%d,b=%f\n"; //使format指向一个字符串 printf(format, a, b);

它相当于

printf("a= %d,b=%f\n", a, b);

因此只要改变指针变量 format 所指向的字符串,就可以改变输入输出的格式。这种 printf 函数称为可变格式输出函数。



【本文地址】


今日新闻


推荐新闻


    CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3