【原创】浅谈指针(七)字符串相关(详细版本)与指针运算

本文原创,仅在博客园发布,若在其他平台发现均为盗取!!!

目录

  • -1、写作目的
  • 0、前言
  • 1、字符串的比较
    • 1.1.历史原因
    • 1.2.数组
    • 1.3.比较函数
  • 2、strcmp函数
    • 2.1.函数规范
    • 2.2.自制strcmp函数
    • 2.3.指针运算
  • 3、strlen函数
    • 3.1.函数规范
    • 3.2.自制strlen
  • 4、strcpy函数
    • 4.1.函数规范
    • 4.2.自制strcpy
  • 5、wchar_t
【【原创】浅谈指针(七)字符串相关(详细版本)与指针运算】
-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 using namespace std; char str[3]; int main(){ printf("%p\n",str); printf("%p\n","abc"); printf("%d\n",sizeof("abc")); }

输出在我的Dev c++环境内显示是:
【原创】浅谈指针(七)字符串相关(详细版本)与指针运算
文章图片

我们可以看到,"abc"字符串常量也是保存在内存中的,保存在只读存储区中。并且,sizeof("abc")返回5,表示"abc"的本质是数组(而不是char的指针,否则会返回4)
1.3.比较函数 既然直接比较s和字符串常量会比较地址,那么,我们只能逐字符进行比较了。
标准库有个函数叫做strcmp,这个函数我们在下一章讨论。
2、strcmp函数 2.1.函数规范 很多字符串处理的函数都包含在中。strcmp就是这样的,使用前需要#include。
strcmp用于比较字符串的大小,只能比较char数组,返回值如下:
strcmp(a,b); /* a=b 返回0 ab 返回正数 */

对于部分处理环境,ab返回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

之前也一定提到过,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 int main(){ wchar_t s[]= L"中文"; for(int i=0; i

输出ascii码。注意,对于DEV C++编译器,需要调整编译选项,不然报错:
【原创】浅谈指针(七)字符串相关(详细版本)与指针运算
文章图片

今天我们讲解了几个常见的C语言字符串函数,以及指针运算。
完。

    推荐阅读