【原創】淺談指針(七)字符串相關(詳細版本)與指針運算


本文原創,僅在博客園發布,若在其他平台發現均為盜取!!!

-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語言字符串函數,以及指針運算。
完。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM