[笔记]指针与字符串
学完指针与函数,指针与数组后,我接触到了新的数据类型,字符串
在C语言中,字符串是字符的集合,可利用数组(指针常量),或者指针变量来表示
- char *str1="Apple ipod";
表示str1指向字符串的第一个字符‘A’,我们用图来详细描述
也可以用数组的表示法,如下所示:
- char str2[10]="Apple iPod"
str2是一个数组名,表示此数组第一个元素的地址(相当于&str2[0]),str+1表示数组的第二个元素的地址(相当于&str2[1]),........以此类推,str2+10则是字符(‘0’)的地址,字符的最后都有一个空字符(‘0’),作为字符串的结束点,若缺少它,将会产生错误的答案
注意点:str1是指针变量,而str2是指针常量,所以str2不可以使用++(自增)和--(自减)运算符。
首先,利用指针变量指向一个字符串,并以字符的格式将字符串输出
#include<stdio.h>
#include<stdlib.h>
int main()
{
char* p = "Apple iPod";
//定义一个指针p,指向字符串Apple iPod的首地址,A的地址
printf("字符串str为:");
for (int i = 0; *p != '\0'; i++)
{
printf("%c", *p);
//结束条件为,输出到(\0)时
p++;
}
return 0;
}
利用循环输出目前指针所指向的字符,直到空字符(‘0’)为止,上例的字符串也可以使用数组的方式加以输出。
在放代码前补一个for循环的小笔记:for(循环初始值,循环判定条件,循环运算)
三个条件三个空间,互不干涉均可省略!
#include<stdio.h>
#include<stdlib.h>
int main()
{
char p[] = "Apple iPod" ;
//定义一个指针p,指向字符串Apple iPod的首地址,A的地址
printf("字符串str为:");
for (int i = 0; p[i] != '\0'; i++)
{
printf("%c", p[i]);
}
return 0;
}
上列代码是我们常用的一种形式,将判断条件改为了p[i]!=0,数组走到‘0’元素时就停止了循环
以上代码输出均为:Apple iPod
其实平常我们最常用,最简单的方法,就是使用%s格式进行输出
#include<stdio.h>
#include<stdlib.h>
int main()
{
char p[] = { "Apple iPod" };
char* p2 = { "Apple iPod" };
printf("%s\n", p);
printf("%s\n", p2);
return 0;
}
可以看到,数组和指针均可用花括号括元素表示,输出结果:
要注意的是,此时变量给予的数组名,如p和p2,因为数组名表示数组第一个元素的地址,所以会从此地址一直打印到字符串的结束点(空字符‘0’)
若将上例代码的p数组元素个素修改为10,将会出错,因为无法找到0,看代码
输出结果:
在输出结果中,p的字符串是错误的,在输出完后乱码,因为分配给p的空间不足,使他无法存放空字符(‘0’),所以经常会将数组下标省略,有程序去自定义
除了在定义字符串变量时,直接给予一个字符串外,也可以利用其他方式得到
上代码:
#include<stdio.h>
#include<stdlib.h>
int main()
{
char* p = { "Learning pointer now" };
char p2[] = "Go ahead";
char* p3;
char p4[20];
p3 = "I want to buy a iPod\0";
printf("C or C++:");
scanf("%s", p4);
printf("\n%s\n", p);
printf("%s\n", p2);
printf("%s\n", p3);
printf("%s\n", p4);
return 0;
}
- p为指针变量,定义时指定一个字符串
- p2为指针常量,为数组,定义时候给予一个字符串
- p3为指针变量,通过赋值给它指定一个字符串
- p4为指针常量,为数组,通过scanf键盘输入给它一个字符串
注意,若将代码中的p3改为数组,p4改为指针变量,则会产生错误
p3为一个数组,它是指针常量,则计算机已经给p3分配好固定的地址,不可以再指定一个新的字符串给它
p4是一个指针变量,不可以由输入的方式来完成指定操作!
指针模拟string库
字符串的操作,最常用的包括:
- 计算字符串长度(string length)
- 进行字符串复制(string copy)
- 进行字符串链接(string concatenate)
- 进行字符串比较(string compare)
要完成这些动作,最方便的是调用字符串的库函数,弹药包含string.h的头文件,这些函数原型全在string的头文件内
除了调用字符串的库函数外,也能利用字符串指针自行编写程序,以模拟这些库函数的功能
计算字符串长度:
字符串的长度表示此字符串有多少个字符,但不包括空字符("0"),计算字符串长度的库函数为strlen,其语法如下:
strlen(str1);//计算str1的长度
我们用指针来实现它:
- 定义一个指针指向一个字符串的首地址
- 自定义函数,将指针传入函数内
- 利用循环语句,指针从首地址指向包含'0'的地址
- 指针每走一次,长度加一,直到走到'0'
#include<stdio.h>
#include<string.h>
int stringlength(char*);
int main()
{
int length = 0;
char* p = "Apple iPod";
printf("使用库函数:%d\n", strlen(p));
length = stringlength(p);
printf("使用自定义函数:%d\n", length);
return 0;
}
stringlength(char* p)
{
int i = 0;//计数功能
while (*p != '\0')
//当指针递增到存有'\0'字符地址时,停止循环
{
p++;
i++;
}
return i;
//返回i
}
运行结果:
在stringlenth函数内,若*p不为空字符,则变量i加1,之后指针再走向下一个字符的地址,上述操作持续进行,直到*为空字符则结束,因为空字符是字符串的结束字符
字符串的复制:
字符串复制的库函数为strcpy,其语法如下:
strcpy(str1,str2);
表示从str2字符串的第一个字符,逐一的赋值到str1字符串,注意:第一个参数str1是接收者,第二个参数str2是给予者
来看下它的使用:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
char* p = "Apple iPod";
char a[80] = {" "};
printf("使用库函数之前: \np=%s\na=%s\n",p,a);
strcpy(a, p);
printf("使用库函数之后:\np=%s\na=%s\n", p, a);
system("pause");
return 0;
}
来看看结果:
若a数组内原来就有字符,则原有字符会被指针p指向的字符串所代替,这里必须小心,以免数据丢失
我们用图片来解释一下它的替代过程:
p指向了字符串的首地址,指针p内存储的是字符‘A’的地址,定义数组时分配了80个char类型的存储空间,由于字符串存储地址具有连续性,则在循环过程中逐个复制了下来
这里特别注意:a数组中要有足够的空间给容纳s字符串
若将上例代码中的
char a[80]=" ";
改为:
char *a
将会有错误产生,因为*a是一个指针变量,且没有一个指向的内存空间,这是我经常会犯错的地方,解决办法就是先分配一个内存给a指针变量,用到malloc来分配
a=(char*)malloc(sizeof*(char));
但是就这样改变的话会发生“访问写入权限冲突”具体解决方案请移步到补充笔记
接下来我们继续参照字符串复制的原理,用自定义函数实现
基本思路:
- 定义一个指针指向字符串首地址
- 定义一个能容纳字符串的数组
- 利用循环让指针从首字符依次走到空字符(0)
- 每走一次将指针走过的字符写入数组内
- 具有连续性
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void StringCopy(char*, char*);
int main()
{
char* p = "Apple iPod";
char a[80] = " ";
printf("传入之前:\np=%s\na=%s\n", p, a);
StringCopy(p, a);
printf("写入之后:\np=%s\na=%s\n", p, a);
system("pause");
return 0;
}
void StringCopy(char* p, char* a)
{
while (*a != '\0')
{
*a = *p;
p++;
a++;
}
}
在StringCopy函数中,利用while循环,将*p的内容赋给*a的内容,待传入到空字符则结束循环,若不是空字符,则将p和a都移步到下一地址,继续执行复制的动作
运行结果:
若我们只需要复制n个字符时候,则调用strning函数,其语法如下:
strning(a,p,n)//将p中的字符串复制到a数组内,复制个数为n
- 定义一个指针指向字符串首地址
- 定义一个能容纳字符串的数组
- 将需要断开的地址赋值为空字符
- 当循环走到空字符时,停止复制
我们用指针来实现它的原理:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void StringCopy(char*, char*);
int main()
{
char* p = "Apple iPod";
char a[80];
a[5] = '\0';
strncpy(a, p, 5);
printf("使用库函数:\np=%s\na=%s\n", p, a);
StringCopy(p, a);
printf("使用自定义函数:\np=%s\na=%s\n", p, a);
system("pause");
return 0;
}
void StringCopy(char* p, char* a)
{
int i = 0;
while (i < 5 && *a != '\0')
{
*a = *p;
p++;
a++;
i++;
}
}
循环条件需要满足i<5,则循环要走五次,还要满足这五次循环中,a的值始终不能为空字符
因为是复制前5个字符,控制循环体的条件可缺一,不过最好全部加上,这样显得目的明确:
与上例代码其实大致相同,只不过多了变量i来控制循环体,同样,如果需要复制n个字符,只需要在下标n处设置空字符即可。
字符串链接:
字符串的链接的库函数为strcat,其语法如下:
strcat(str1,str2);
表示将str2字符串连接到str1字符串的尾端,注意,str1必须要分配足够的空间
- 定义两个需要链接的字符
- 注意需要嵌入字符串的内存要足够容纳需要嵌入的字符串
- 利用循环先走到需要嵌入的字符串尾端,不包含空字符
- 利用循环将嵌入的字符串嵌入进数组内
- 尾端的空字符会被代替
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void StringConcat(char*, char*);
int main()
{
char a[80] = "I want to buy a ";
char* p = "iPhone";
char b[80] = "I want to buy a ";
strcat(a, p);
printf("使用库函数:%s\n", a);
StringConcat(b, p);
printf("使用自定义函数:%s\n", a);
system("pause");
return 0;
}
void StringConcat(char* b, char* p)
{
while (*b != '\0')
{
b++;
}
while (*b != '\0');
{
*b = *p;
b++;
p++;
}
}
那么这时我产生了一个疑问,能不能将数组嵌入到p后面呢?通过测试,是不行的
我用的是strcat函数直接进行链接,因为p为指针变量,它指向的为一个地址
给它指定一个字符串,它所含的值也是字符串首字符的地址,它可以通过运算走过相邻的地址,但是它无法在所指向之外的地址存入值,所以无法完成连接
而数组可以是因为在数组定义的时候,计算机并分配了n个连续性同类型的内存空间给它,它所有的地址都是相邻的,已经分配好属于它的,所以在未越界的情况下,可以在地址内存储,修改值
在上述代码的stringconcate函数中,利用循环先追踪到a所存储的字符串尾端,接下来,将p指针指向的字符串(p)指定给(a),直到(*p)是空字符为止
若只是要链接n个字符,则可调用strncat库函数,其语法如下:
strncat(str1,str2,n);
意思是将str2的前n个字符链接到str1
与上述思路一样,我们利用循环次数来控制复制量
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void StringConcat(char*, char*, int);
int main()
{
char a[80] = "I want to buy a ";
char* p = "iPhone";
char b[80] = "I want to buy a ";
strncat(a, p, 5);
printf("使用库函数:%s\n", a);
StringConcat(b, p, 5);
printf("使用自定义函数:%s\n", a);
system("pause");
return 0;
}
void StringConcat(char* b, char* p, int n)
{
int i = 0;
while (*b != '\0')
{
b++;
}
while (i < n && *b != '\0')
{
*b = *p;
b++;
p++;
}
}
运行结果:
字符串比较
字符串比较也可以说是最常用的,若要比较两个字符串是否相等,有些人可能会这样编写代码
#include<stdio.h>
#include<stdlib.h>
int main()
{
char str1[] = "Honda Civic";
char str2[] = "Honda Civic";
printf("str1指向的地址:%x\n", str1);
printf("str2指向的地址:%x\n", str2);
if (str1 == str2)
{
printf("str1等于str2\n");
}
else
{
printf("str1不等于str2\n");
}
system("pause");
return 0;
}
这段代码是在比较str1和str2的地址是否相等,而不是比较内容是否相等,因为str1和str2是数组名,表示第一个元素的地址,所以这段内容并不是在比较字符串的内容
输出结果:
如果使用指针变量作比较,这会有不一样的结果:
#include<stdio.h>
#include<stdlib.h>
int main()
{
char* p = "Hello Word";
char* p2 = "Hello Word";
char* p3 = "Hello Jiajia";
printf("p所指向的地址:%x\n", p);
printf("p2所指向的地址:%x\n", p2);
printf("p3所指向的地址:%x\n", p3);
if (p == p2)
{
printf("p等于p2\n");
}
else
{
printf("p不等于p2\n");
}
if (p == p3)
{
printf("p等于p3\n");
}
else
{
printf("p不等于p3\n");
}
system("pause");
return 0;
}
上述代码也是在比较地址,因为p,p2,p3均为指针变量,指向的都是一段字符串的首地址,p和p2指向的是相同的字符串,则它两相等,而p3指向的则是不同的字符串,所以它与p和p2都不想等
也可以使用malloc动态内存分配函数,先分配内存,再指定一个字符串给它,如:
输出结果也与上述代码相同
但我们一般不会使用上述两种方式来比较字符串是否相等,这里的用意是为了了解这样比较的方法是错误的,也更加加强了指针与字符串之间的关系,
当然是用strcmp函数来比较字符串是最正统的,strcmp函数在头文件string函数内,其语法如下:
strcmp(p,p2);//表示比较p与p2字符串是否相等
我们用代码来看看:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int stringCompare(char*, char*);
int main()
{
char* p = "Hello Word";
char* p2 = "Hello China";
int value;
value = strcmp(p, p2);
printf("使用库函数比较:\n");
strcmp(p, p2);
switch (value)
{
case 0:
printf("p等于p2\n");
break;
case 1:
printf("p大于p2\n");
break;
case -1:
printf("p小于p2\n");
break;
}
printf("使用自定义函数比较:\n");
value = stringCompare(p, p2);
if (value == 0)
{
printf("p等于p2\n");
}
else if (value > 0)
{
printf("p大于p2\n");
}
else
{
printf("p小于p2\n");
}
system("pause");
return 0;
}
int stringCompare(char* x, char* y)
{
//让指针走过字符串每一个字符
//当字符串字符不相等时停止循环
while (*x == *y)
{
if (*x == '\0')
//判断字符串是否为0
{
return 0;
}
x++;
y++;
}
return *x - *y;
}
我们来用图剖析自定义函数体内的代码
在stringcompare函数中,比较x与y的内容是否相等,假如相等,则再判断*x是否为空字符,若不是,则将*x与*y的内容相减后返回
输出结果:
在循环到空格的时候,x++,y++指向对应的字母分别为W和C,转换成ASCII码做比较,W为87,C为67,则p大于p2
有时在比较两个字符串时,并不需要比较整个字符串,只要比较前面几个字符即可,可用到strncmp函数,其格式为
strncmp(str1,str2,n)//比较str1与str2前五个字符的大小
我们用代码来实现:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int stringCompare(char*, char*, int n);
int main()
{
char* p = "Hello Word";
char* p2 = "Hello China";
int value;
value = strncmp(p, p2,5);
printf("使用库函数比较:\n");
strcmp(p, p2);
switch (value)
{
case 0:
printf("p等于p2\n");
break;
case 1:
printf("p大于p2\n");
break;
case -1:
printf("p小于p2\n");
break;
}
printf("使用自定义函数比较:\n");
value = stringCompare(p, p2, 5);
if (value == 0)
{
printf("p等于p2\n");
}
else if (value > 0)
{
printf("p大于p2\n");
}
else
{
printf("p小于p2\n");
}
system("pause");
return 0;
}
int stringCompare(char* x, char* y, int n)
{
//让指针走过字符串每一个字符
//当字符串字符不相等时停止循环
int i = 0;
while (i < n && *x == *y)
{
if (*x == '\0')
//判断字符串是否为0
{
return 0;
}
x++;
i++;
y++;
}
return *x - *y;
}
与复制,连接一样,通过循环变量来控制循环次数(比较次数),可以看到
多了变量i和参数n,起到一个控制循环次数的作用,输出结果:
总结:
指针与字符串我们清楚了指针常量和变量的区别和关系
数组可看做指针常量,在定义的时候,计算机就已经分配好了固定的内存给它,这段地址是不可修改,只能存储
当一个字符串赋值给数组时,是将字符串每个字符存入数组内的空间
当一个字符串赋值给指针时,是将指针指向该字符串的首地址,如果在定义指针时并未指定,则不可通过赋值给指针
如果一定要赋值,则需要动态分配一片内存给指针,再指定一个字符串给它
当需要用户输入的时候,指针不可以指定键盘输入的元素,俗点说,指针只能指向已知的数据,否则会像无头苍蝇一样。如果需要用户输入,则动态malloc分配一片内存给指针
而数组可以,因为数组已经是一片固定内存,当用户输入时,用户输入的数据会存入该数组。
数组相当于一个公用箱,你不能动它,但是你可以存取东西,而指针相当于一个私人用箱,你可以随意修改己值和他值。当然,指向的数据类型需要是变量。
而指针*和数组[]均有取值功能。
在本章中模拟string库函数,原理不过是通过指针去修改,存储地址内的值
- 复制就是指针走过字符串所有字符,然后依次将其存入未被占用的地址内
- 连接就是指针走过所有字不包含空的字符,然后在尾端执行复制
- 比较就是指针依次走过所有字符,将字符转换成ASCII码进行比较
- 计数就是指针依次走过所有字符,每走过一个,计数变量递增一次