C語言03- 數組、字符串
10:數組
數組每個元素的類型相同综液,因此各個元素在內存中存放的長度也一樣款慨,即它們占用的空間是等長的,而數組名谬莹,就是這段內存的首地址檩奠。
數組是連續(xù)存儲桩了,所以它支持隨機訪問.
10.1:一維數組
下面關于數組的一些定義,有的是錯誤的:
int x = 10;
int a[x]; //錯誤笆凌,x是變量而不是常量
const int x1 = 10;
int a1[x1];//正確圣猎,x現在是常變量
int a2[10];//正確,10是整數常量
一些常見的一維數組定義:
int a[100];
char a[100];
int *a[10];//每個元素是一個指針
a[i]:數組元素值
&a[i]:第i個元素的地址
&a[0]:首個元素的地址
a乞而,首地址
a5+1
&a[0]+1
sizeof是用來計算類型和數據的長度送悔。strlen是用來計算字符串中非‘\0’的字符個數的。
- sizeof(a):數組的長度計算
- sizeof(a[0]):數組中一個元素的 長度
- n=sizeof(a)/sizeof(a[0]):數組元素個數
- #define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0]))
10.2:二維數組
二維數組可以理解為數學中的矩陣的一個類似概念爪模。二維數組定義的一般形式為:
類型說明符 數組名[常量表達式1][常量表達式2]
二維數組的初始化可以為下面的形式:
int a[5][3]={ {80,75,92},{61,65,71},{59,63,70},{85,87,90},{76,77,85} };
對于二維數組的遍歷欠啤,可以先列后行(即先遍歷列,再遍歷行)屋灌,也可以先行后列(即先遍歷行洁段,再遍歷列)進行。比如共郭,對于一個m行n列的整型二維數組a[m][n]進行遍歷:
//先行后列:
for(int i = 0; i < m;i++) {
for(int j = 0; j < n; j++) {
printf(“%d/n”, a[i][j]);
}
}
//輸出為:a[0][0] a[0][1] a[0][2] a[0][3]…a[0][9]...
//先列后行:
for(int i = 0; i < n;i++) {
for(int j = 0; j < m; j++) {
printf(“%d/n”, a[j][i]);
}
}
//輸出為:a[0][0] a[1][0] a[2][0] a[3][0] a[4][0]
由于數組是按行的順序存放的祠丝,同一列的數據就有可能存放在不同的頁中,那么先列后行的訪問將引起更多的缺頁中斷除嘹,降低了遍歷的效率台颠。
10.3:數組重要注意事項
int a1[10];
int a2[4][5];
//a1,&a1,a2,&a2都是數組的首地址迹卢,值相同擅腰,但是類型不同毕匀。
//類型為什么不一樣呢?
//a1:int * -
//&a1:int (*a1)[10] -包含十個元素的一維數組的指針類型
//a2:int (*a2)[5] -數組指針年缎,指向含有5個元素的數組悔捶,代表一行
//&a2:int (*a2)[4][5] -數組指針,指向還有4行5列的一個數組
--------------------------------------------
int a[3] = {0,1,2};
printf("a:%p\n", a);//a是int *類型单芜;0x7ffee02f693c
printf("&a:%p\n", &a);//&a是int (*a)[3]類型蜕该;0x7ffee02f693c
printf("&a[0]:%p\n", &a[0]);//首個元素的地址;0x7ffee02f693c
//打印輸出a, &a, &a[0]值一樣缓溅;如地址0x7ffee02f693c
//sizeof判斷數據類型長度符的關鍵字;其作用就是返回一個對象或者類型所占的內存字節(jié)數
/*
指針變量的sizeof
學過數據結構的你應該知道指針是一個很重要的概念蛇损,它記錄了另一個對象的地址。既然是來存放地址的淤齐,那么它當然等于計算機內部地址總線的寬度。所以在32位計算機中袜匿,一個指針變量的返回值必定是4(注意結果是以字節(jié)為單位)更啄,但是,在64位系統(tǒng)中指針變量的sizeof結果為8居灯。
*/
printf("sizeof(a):%lu\n", sizeof(a));//數組的sizeof值等于數組所占用的內存字節(jié)數
printf("sizeof(&a):%lu\n", sizeof(&a));//
printf("sizeof(&a[0]):%lu\n", sizeof(&a[0]));//
// sizeof(a):12
// sizeof(&a):8 //64位系統(tǒng)
// sizeof(&a[0]):8 //64位系統(tǒng)
printf("a:%p\n", a + 1);//a是int *類型祭务,0x7ffee2d5c940 加一是其類型加一
printf("&a:%p\n", &a + 1);//&a是int (*a)[3]類型内狗,0x7ffee2d5c948
printf("&a[0]:%p\n", &a[0] + 1);//首個元素的地址,0x7ffee2d5c940
---------------------------
一般地:
int a[m1][m2]...[mn]
a, &a, &a[0]...[0]
a+1//a的類型int (*a)[m2]...[mn],一行
&a+1//&a的類型int (*a)[m1]...[mn]
&a[0]...[0]//代表的第一個元素的類型int *
int a[5] = {1,2,3,4,5};
int *ptr1 = (int *)(&a+1);
int *ptr2 = (int *)((int)a+1);
printf("%x, %x", ptr1[-1], *ptr2)
//打印0x5(0x00000005), 0x02000000;
/*
ptr1[-1]:ptr1類型是int *义锥,ptr1[-1]相當于ptr1-1既ptr1減去int *類型長度4的地址柳沙。
*/
10.3.1:數組做參數傳遞給函數,在函數內部退化為指針
void func(int a[], size_t len) {//a是數組名拌倍,len是數組元素個數赂鲤。數組做函數參數,一般都這么傳遞
printf(“sizeof(a) in func=%d\n”, sizeof(a));
}
int a[10] = {0};
printf(“sizeof(a)=%d\n”, sizeof(a));
func(a, 10);
/*
執(zhí)行上面的代碼柱恤,你會發(fā)現輸出的兩個結果為:
sizeof(a) = 40
sizeof(a) in func=4
也就是說数初,如果數組做了函數的參數,那么在函數內部梗顺,數組就變?yōu)榱酥羔樑莺ⅰ嶋H上,數組是一個常量指針寺谤。比如:
int a[10];
a的類型為:int *const a;//a is a const pointer to int;
*/
10.3.2:數組溢出與預防
會造成程序的異陈嘏福現象;C語言編譯器對數組溢出不做檢測变屁,
- 學了函數之后會發(fā)現程序1锈候,無法退出,i=16之后敞贡,又會重新被置為0。
- 程序2摄职,a[9]不存在誊役,越界溢出
11:字符串
字符串是編程語言中表示文本的數據類型。
11.1:字符串定義
字符串是由零個或多個字符組成的有限序列谷市。C語言字符串可以定義為:”c1c2c3c4..cn\0”蛔垢。
C語言的字符串是以’\0’結尾的。
程序在存放字符串的時候迫悠,會自動在字符后面加上一個’\0’作為結尾鹏漆。
11.1.1:轉義字符
C語言中常見的轉義字符如下:
11.2:程序中的字符串
- "a":字符串,存儲在靜態(tài)區(qū)创泄。實際上是2個字符艺玲,末尾包好'\0'。使用方法char *str = "a";也就是可以賦值給字符指針鞠抑,這個時候str指向"a"的首地址饭聚。
- 'a':字符常量,值類型搁拙,不存儲在靜態(tài)區(qū)秒梳,無內存法绵。使用char ch='a';賦值給ch變量,ch存放在哪里酪碘,'a'就存放在哪里朋譬。不能使用char *p='a';因為'a'對應的類型是char。
并不是所有的常量都會被編譯器放在常量區(qū)的兴垦,編譯器認為普通的整型徙赢、浮點型或字符型常量在使用的時候是可以通過立即數來實現的,沒有必要額外存儲到數據區(qū)滑进,如此節(jié)省了存儲空間和運行時的訪問時間犀忱。
常量區(qū)里存放的是一些不可改變的量,比如字符串常量扶关。在實際的ELF(Executable and Linkable Format阴汇,可執(zhí)行連接格式,是UNIX系統(tǒng)實驗室(USL)作為應用程序二進制接口(Application Binary Interface节槐,ABI)而開發(fā)和發(fā)布的搀庶。Linux作為類unix系統(tǒng)其程序仍然沿用了該格式。)的程序數據是分段存儲的铜异,對應的常量區(qū)就是“.rodata(只讀數據)段“哥倔。
常量區(qū)的數據被標記為只讀,也就是程序只有訪問權而沒有寫入權揍庄,因此如果開發(fā)者需要使用某些不希望被改變的數據時可以將其放入常量區(qū)咆蒿。
在C語言中常量有很多種,比如常見的:
字符常量:‘a’蚂子, ’A’沃测, ’*’。
字符串常量:”helloworld”食茎,”ilovechina”蒂破,”12345”。
整常量: 25别渔,10附迷,012,0x0a哎媚,0b00001010喇伯。
浮點常量: 3.14,123.456抄伍, 3.0E-23艘刚;
11.2.1:多字節(jié)字符串與寬字符字符串
- char表示一個ASCII字符占用1個字節(jié),char類型字符串以”\0”結尾截珍。
- wchar_t表示一個UNICODE字符占用2個或4個字節(jié)攀甚,wchar_t類型字符串以”\0\0”結尾箩朴。
多字節(jié)字符串
在多字節(jié)字符串中,每個字符的編碼寬度都不等秋度,可以是一個字節(jié)炸庞,也可以是多個字節(jié)。比如:
char *str = “Hello, world!您好荚斯,世界埠居!”。
上面是一個多字節(jié)字符串事期。其中的英文字符占一個字節(jié)滥壕,而中文字符占2個字節(jié)。
寬字符字符串
而寬字符串中兽泣,每個字母占用的字節(jié)數是一樣的绎橘。比如:
wchar_t *wstr = L“Hello, world!您好,世界唠倦!”
上面是一個寬字符串称鳞,每個字符,無論英文字母還是中文字符稠鼻,都占2個字節(jié)冈止。
scanf_s("%ws", s, 100)//不能有空格,有空格打斷
fgetws(s, 100, stdin)
fputws(s, stdout)
用wctomb()等函數將寬字符串與多字節(jié)串進行相互轉換
11.2.2:使用方式
字符串常量可以賦值給一個字符指針或者一個字符數組候齿,比如:
- char *str = “this is a string”;
- char str2[]= “this is a string”;
- char str3[100] = “this is a string”;
語句1將”this is a string”賦值給了字符指針str熙暴。此時,str的值為”this is a string”的第一個字符的地址慌盯≡惯洌”this is a string”這個常量字符串存儲在內存常量區(qū)。而str即指向了存儲這個常量字符串 的首地址润匙。
語句2會將常量區(qū)中的”this is a string”拷貝到數組里面。并且數組的長度將為”this is a string” (包含’\0’)的長度唉匾。
語句3會將常量區(qū)中的”this is a string”拷貝到數組里面孕讳。并且數組的長度將為100個字節(jié)。語句3和語句2的區(qū)別是語句2沒有指明數組的長度巍膘,那么數組的長度就是字符串的長度厂财。
sizeof(str);//為指針的長度,所以在X86上是4峡懈,在X64上是8璃饱。
sizeof(str2)=17;//str2數組的長度,但str2沒有顯示指出數組的長度肪康,而是按照分配給它的字符串的長度來分配荚恶。所以撩穿,值為17。
sizeof(str3)=100;//sizeof計算的是str3數組的長度谒撼,所以結果為100食寡。
strlen(str)=16;//strlen計算的是字符串的字符個數(不包含’\0’)。
strlen(str2)=16;//原因同上廓潜。
strlen(str3)=16;//原因同上抵皱。
//把字符串存放在動態(tài)分配的內存空間中
char *p = (char *)malloc(100);
if (p == NULL)
return;
memset(p, 0, 100);
strcpy(p, “hello world”);//以p為首地址的內存中存這個字符串。
1辩蛋,while循環(huán)遍歷
char *str=“hello world!”;
while(*str!=‘\0’) {
printf(“%c”, *str);
str++;
}
11.3:字符串API
- strlen:計算字符串中字符個數(不含結尾字符’\0’)呻畸;
- strstr:查找字符串中子串的位置
- strcmp/stricmp:比較2個字符串是否相等,stricmp()多了個i(ignore)比較時忽略大小寫
- strchr/strrchr:strchr從左邊開始在字符串中查找某個字符的位置悼院,strrchr()右邊開始
- strcpy/strcpy_s:strcpy將字符串復制到目標緩存,但不檢測目標緩存的長度是否大于字符串的長度伤为,無論是否超過,都照拷不誤;在Windows平臺推出了新的安全拷貝函數strcpy_s()樱蛤。strcpy_s將檢測字符串的長度是否超過了目標緩存的大小钮呀,如果超過,則拒絕拷貝并返回失敗昨凡。
- strcat/strcat_s:用于字符串的拼接爽醋,并不檢測目標緩沖的大小是否能夠容納下拼接之后的字符串,因此也容易造成緩沖區(qū)溢出漏洞便脊。strcat_s()避免了這個問題蚂四。
- strtok/strtok_s: 將字符串s拆分為為一組字符串,delim為分隔符哪痰。
11.4:自己實現字符串API
size_t _strlen(const char *str) {
size_t count = 0;
while(*str++!= '\0') {
count++;
}
return count;
}
char *_strcpy(char *dst, char *src) {
char *s = dst;//如果不用臨時指針遂赠,最后dst指向尾部
while(*s++ = *src);
return dst;
}
/*
int strcmp(char *s1,char * s2),它的功能:比較字符串s1和s2:
當s1<s2時晌杰,返回值<0
當s1=s2時跷睦,返回值=0
當s1>s2時,返回值>0
*/
int _strcmp(const char *s1, const char *s2) {
assert(s1!==NULL && s2!=NULL)
while(*s1 && *s2 && (*s1==*s2)) {
s1++;
s2++;
}
return *s1-*s2
}
/*
strstr()的原型為:char *strstr(char *s1, char *s2)
功能是從字符串s1中尋找s2第一次出現的位置(不比較結束符NULL)肋演。
*/
char *_strstr(char *s1, char *s2) {
if (s1== NULL || s2==NULL)
return NULL;
if (!*s2)
return ((char *)s1);
char *s3, *s4;
char *p = (char *)s1;
while(*p) {
s3 = p;
s4 = s2;
while(*s3 && *s4 && !(*s3-*s4))//*s3-*s4相等為0
s3++, s4++;
if(!*s4)//上面while在相等之后抑诸,s4指針到達尾部
return p;
p++;
}
return NULL;
}
/*
strtok()的原型為:char *strtok(char *s, char *delim)
功能為:分解字符串為一組字符串。s為要分解的字符串爹殊,delim為分隔符字符串蜕乡。
*/
//由于此函數的功能比較難理解,在給出實現算法前先看看它的具體使用例子:
char s[] = "Nice to meet you!";
char *d = " ";
char *p = NULL;
p = strtok(s, d);
while(p) {
printf("%s\n",p);//”Nice” “to” “meet” “you!”
p = strtok(NULL,d);
}
//實現暫略
/*tolower()的原型:char tolower(char ch)
功能為:將大寫字母轉換為小寫字母
*/
char _tolower(char ch) {
if(ch>=a && ch<=z) {
return ch;
}
if(ch>=A && ch=Z) {
return (ch + 'a' - 'A');
}
}
//刪除特定字符
void deleteChar(char *str, char c) {
assert(str != NULL);
char *tmp = NULL;
while(*str != '\0') {
if(*str != c) {
*tmp++ = *str;
}
*str++;
}
}
void deleteChar(char *str, char c) {
assert(str != NULL);
int iDes = 0, iSrc = 0;
do {
if (str[iSrc] != c)
str[iDes++] = str[iSrc];
} while(str[iSrc++] != '\0');
}
//刪除特定字符組void deleteChars(char *str, char chr[], int n)
//思路從字符串str中尋找chr第一次出現的位置,然后跑步指針賦值移位梗夸。
//逆置字符串
void reverseString(char *str) {
char c;
int n = strlen(str);
for (int i = 0; i < n/2; i++) {
c = str[i];
str[i] = str[n-1-i];
str[n-1-i] = c;
}
}