【原创】浅谈指针(七)字符串相关(详细版本)与指针运算
本文原创,仅在博客园发布,若在其他平台发现均为盗取!!!
-1、写作目的
昨天我写过一个版本的字符串相关,浅谈指针系列:https://www.cnblogs.com/jisuanjizhishizatan/p/15545229.html
这篇文章由于时间仓促,写的比较水,因此今天有时间我来重置一篇,稍微写详细一点。
0、前言
字符串一直是C++的一个难点。昨天某个同学问我,关于字符串的比较问题:
char s[10];
...
if(s=="123456789")...
没有语法错误,但是运行结果不太对。
大家可以在这个地方停一停,想想是为什么。
1、字符串的比较
1.1.历史原因
最初,C语言刚刚面世的时候,还没有我们现在经常写C++时使用的string类型。当时的人们使用字符串,必须使用char的数组模拟字符串。
例如,输出hello world,是这样写的:
char s[]={'H','e','l','l','o',' ','w','o','r','l','d','\0'};
for(int i=0;i<11;i++)putchar(s[i]);
这样的写法是很麻烦的,对此,C语言使用了""运算符,以及printf的%s参数。
char s[]={"Hello world"};
printf("%s",s);
这里注意,""运算符,返回的是一个char数组,如果在C++这样写:
string s="hello";
其本质是string类的运算符重载,重载了赋值运算符=。而并非表示""返回的是string类型。
1.2.数组
我之前应该写过(不过我忘记在哪篇里面提到过),数组名在表达式中,某些情况是可以解释做指针的。
也就是说,例如:
int a[10];
int *p;
p=a;
在这里,a表示指针,也就是数组a的首个元素的地址。
因此,执行这样的比较:
s=="123456789"
本质是对s的地址和"123456789"的地址进行比较。
我们来看一个简单的程序:
#include<bits/stdc++.h>
using namespace std;
char str[3];
int main(){
printf("%p\n",str);
printf("%p\n","abc");
printf("%d\n",sizeof("abcd"));
}
输出在我的Dev c++环境内显示是:
我们可以看到,"abc"字符串常量也是保存在内存中的,保存在只读存储区中。并且,sizeof("abcd")返回5,表示"abcd"的本质是数组(而不是char的指针,否则会返回4)
1.3.比较函数
既然直接比较s和字符串常量会比较地址,那么,我们只能逐字符进行比较了。
标准库有个函数叫做strcmp,这个函数我们在下一章讨论。
2、strcmp函数
2.1.函数规范
很多字符串处理的函数都包含在<string.h>中。strcmp就是这样的,使用前需要#include<string.h>。
strcmp用于比较字符串的大小,只能比较char数组,返回值如下:
strcmp(a,b);
/*
a=b 返回0
a<b 返回负数
a>b 返回正数
*/
对于部分处理环境,a<b返回-1,a>b返回1,但是如果仅仅对1和-1判断,在部分环境还是过不去的,因为标准库没有这样规定。
2.2.自制strcmp函数
int strcmp(char *s1,char *s2){
while(*s1==*s2 && *s1 && *s2){
s1++;s2++;
}
return *s1-*s2;
}
上面的strcmp函数是我自己写的,其中使用了简单的指针运算。
在这里,s1和s2就是s1和s2指向的字符,如果字符相等,且两个字符不为0,那么就继续进行比较,也就是*s1 && *s2
的含义。
s1-s2就是对字符作差,也就是对字符进行比较,如果大于返回正数,小于则返回负数。
2.3.指针运算
实际上,对指针进行加减运算(例如s1++一类的语句)叫做指针运算。在有些情况下,指针运算是晦涩难懂的,例如:
while(*p++);
相信大家都没看懂这句话是什么意思,实际这是strlen的实现代码中的一句,应该都没想到吧。
下面是我仿照的strlen实现,不太容易看懂的版本
int strlen(char *s){
char *p=s;
while(*p++);
return p-s-1;
}
那么,我们为什么需要指针运算呢?首先,在很久以前的C语言中,遍历数组,需要使用到如下的语句:
for(int i=0;i<n;i++){
//对a[i]进行操作
}
之前也一定提到过,a[i]是*(a+i)的缩略形式,那么,在循环体内,编译器需要计算多次a+i。
但是,如果使用指针运算:
for(int *p=a;*p!=a+n;p++){
//对p进行操作
}
这样,使用p来代替数组中多次出现的a[i],a+i中的加法运算只会在结束时执行一次。
因此,在以前的C语言中,使用指针运算可以加快程序的运行速度。但是,现在已经不是这样了。以下引用自《征服C指针》:
如今,编译器在不断地被优化,对于循环内部重复出现的表达式的集中处理,是编译器优化的基本内容。对于现在一般的 C 编译器,无论你使用数组还是指针,效率上都不会出现明显的差距。基本上都是输出完全相同的机器码。
总的来说,C 的指针运算功能的出现,源自于早期的 C 自身没有优化手段。这一点并不奇怪,请大家回想一下在前面介绍过的内容,C 本来只是为了解决开发现场的人们眼前的问题而出现的一种语言。Unix 之前的 OS 几乎都是使用汇编写的,即使晦涩难懂,人们也不会大惊小怪。对于当时的环境,追求什么编译器优化实在有点勉为其难。因此,当初开发 C语言的时候,是完全有必要提供指针运算功能的。可是……
这应该就是大家认为“指针很难懂”或是“指针容易出错”的根本原因,都是复杂的指针运算所致的。
当然,凡事都有特例,比如,在“一个巨大的 char 数组中,参杂了各种类型的数据,并且我们试图读取第多少字节的数据”这样的情况下,还是使用指针运算写的程序比较容易理解。
无论如何,作为一个C++的程序员,我们应该学会阅读一些基本的指针运算。至于写指针运算,在非特殊情况下,我们还是一般不要使用指针运算,这样会降低程序的可读性。
3、strlen函数
3.1.函数规范
strlen(s)返回s的长度,不计'\0',只能用于char数组。
3.2.自制strlen
int strlen(char *s){
int ret;
for(ret=0;s[ret]!='\0';ret++);
return ret;
}
之前那个指针运算的strlen可能比较难懂,我们使用数组再写一个出来。这次,我们只对ret进行加法运算,如果s[ret]等于结束符'\0'表示字符串结束,那么就把他作为循环的条件。这样使用for循环来写,可能更加容易懂一些。
4、strcpy函数
4.1.函数规范
如果想要对字符串进行拷贝,例如把char数组的字符串s赋值为"abc",那么,
s="abc";
一句,如果s是数组将会报错,如果s为指针,那么会使得s指向字符串常量"abc",从而导致s是不可变的。
那么,我们需要使用strcpy函数:
strcpy(s,"abc");
4.2.自制strcpy
void strcpy(char *dest,const char *src){
int i=0;
while(src[i]!='\0'){
dest[i]=src[i];
++i;
}
}
这次我们同样没有使用指针运算。事实上,不使用指针运算的程序,在长度上并没有很长,使用指针运算也不太能够使程序简洁。
5、wchar_t
wchar_t是C95新增的一个功能,表示宽字符,可以存储中文。对应的字符串函数,需要使用wcscpy,wcslen等函数,使用方法与char类似。
这一部分只是简略提到一下,例如如下的程序:
#include<bits/stdc++.h>
int main(){
wchar_t s[]= L"中文";
for(int i=0;i<wcslen(s);i++)printf("%d ",s[i]);
}
输出ascii码。注意,对于DEV C++编译器,需要调整编译选项,不然报错:
今天我们讲解了几个常见的C语言字符串函数,以及指针运算。
完。