C語言提高
- CS和BS的區別
- 函式封裝和陣列形參退化為指標
- 資料型別本質
- 變數的本質
- 記憶體磁區模型
- 全域區
- 以文字常量區為例分析全域區
- 堆疊區
- 堆區
- 函式的呼叫模型
- 函式呼叫變數傳遞分析
- 靜態區域變數的使用
- 堆疊地址的生長方向
- 堆地址的生長方向
- 記憶體的存放方向-以陣列為例
- 指標強化
- 強化一
- 指標也是資料型別
- 通過*來操作記憶體
- 易錯點
- 寫記憶體時,一定要確保記憶體可寫
- 不允許向NULL和未知非法地址拷貝記憶體
- 強化二
- 怎樣定義一個變數來保存自身的地址?
- 主調函式和被調函式
- 記憶體分配方式
- 輸入特性
- 輸出特性
- 字串初始化
- 陣列法和指標法操作字串
- 自己寫字串拷貝函式
- 完善字串拷貝函式
- 專案開發常用字串應用模型
- strstr中的while和do-while模型
- do-while模型
- while模型封裝成函式
- 兩頭堵模型
- 求兩頭堵模型下非空字串的個數
- 求兩頭堵模型下非空字串并輸出
- const的使用
- 修飾一個變數為只讀
- 修飾普通變數為只讀
- 修飾普通指標變數為只讀
- 修飾結構體變數為只讀
- 如何參考另一個c檔案中使用const修飾的變數
- C語言中const是一個冒牌貨
- 二級指標
- 二級指標做函式引數---輸出特性
- 二級指標做函式引數---輸入特性
- 第一種輸入模型
- 指標陣列的定義與使用
- 對指標陣列中的成員進行排序
- 對上述代碼進行函式封裝
- 第二種記憶體模型
- 封裝成函式
- 第三種記憶體模型
- 匯入
- 封裝函式
- 封裝函式改進-多級指標的使用
學習網址: https://www.bilibili.com/video/BV1Rt411m78c?p=8.
CS和BS的區別

函式封裝和陣列形參退化為指標
因為陣列做形參會退化為指標,所以,如果在被調函式中涉及到陣列的長度的時候,只能把n作為引數傳進去(只能在主函式中求出),而在被調函式中求不出來,
//如果陣列作為函式引數,陣列形參退化為指標,
//void print_array(int a[10], int n)
//void print_array(int a[1], int n)
//void print_array(int a[], int n)
//[]中的值寫不寫都行.
void print_array(int *a, int n)
{
//a,當作指標用,指標型別,32位,長度是4個位元組
//n = 4 / 4 = 1
n = sizeof(a) / sizeof(a[0]);//求陣列中元素的個數
printf("print_array:n = %d.\n", n);
int i;
for (i = 0; i < n; i++)
{
printf("%2d ", a[i]);
}
printf("\n");
}
呼叫它
print_array(a, n);//傳陣列名,就是陣列的首地址,
不能填0

寫大于0的值都沒關系,因為都是把它當作傳入陣列的首地址來操作的,所以干脆直接寫成指標,
資料型別本質
資料型別的本質:固定記憶體塊大小的別名
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
int a[10];//告訴編譯器,分配40個位元組
int b; //告訴編譯器,分配4個位元組
/* 型別的本質:固定記憶體塊大小的別名 */
printf("sizeof(a) = %d, sizeof(b) = %d \n", sizeof(a), sizeof(b));
/* 列印地址 */
// 陣列名字,陣列首元素地址,陣列首地址
printf("a:%d, &a:%d \n",a,&a);
//a和&a的陣列型別不一樣
//a是陣列首元素地址,一個元素是4位元組,+1相當于加4個位元組
//&a是整個陣列的首地址,一個陣列4*10 = 40位元組,+1相當于加40個位元組
printf("a+1:%d, &a+1:%d \n", a+1, &a+1);
//指標型別的長度,32位程式,長度是4
// 64位程式,長度是8
char****************** p = NULL;
int* q = NULL;
printf("%d,%d\n",sizeof(p),sizeof(q));
system("pause");
return 0;
}

變數的本質
變數本質:一段連續記憶體空間的別名
變數相當于門牌號,記憶體相當于房間
記憶體磁區模型
C++程式在執行時,將記憶體大方向劃分為4個區域
? 代碼區:存放函式體的二進制代碼,由作業系統進行管理的
? 全域區:存放全域變數和靜態變數以及常量
? 堆疊區:由編譯器自動分配釋放, 存放函式的引數值,區域變數等
? 堆區:由程式員分配和釋放,若程式員不釋放,程式結束時由作業系統回收
全域區
以文字常量區為例分析全域區
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char* get_str1()
{ // p 是堆疊區
char* p = "abcdefg";// "abcdefg\0"放在文字常量區(全域區)
return p;
}
char* get_str2()
{
char* q = "abcdefg";//文字常量區(全域區)
return q;
}
int main(void)
{
char* p = NULL;
char* q = NULL;
p = get_str1();
q = get_str2();
//%s:指標指向記憶體區域的內容
//%d:列印p本身的值
printf("p = %s,p = %d\n",p,p);
printf("q = %s,q = %d\n",q,q);
system("pause");
return 0;
}
搞錯的點:
//%s:指標指向記憶體區域的內容
//%d:列印p本身的值
printf(“p = %s,p = %d\n”,p,p);
運行結果:p、q指向同一塊記憶體,

畫圖分析:

堆疊區
與上面的有區別的,上面是指標,這里是陣列,
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char* get_str()
{
char str[] = "abcdefg";
return str;
}
int main(void)
{
char buf[128] = { 0 };
strcpy(buf, get_str());
//%s:指標指向記憶體區域的內容
//%d:列印p本身的值
printf("buf = %s.\n",buf);
system("pause");
return 0;
}
"abcdefg\0"在全域區,
畫圖分析:

運行結果:

從這里看是仍保留著之前的內容,但是,如果是在一個很大的程式中,就有可能存在著程式崩潰的隱患,或者在不同的編譯器中,輸出的結果有可能是亂碼,
這個例子還不太好說明問題,因為有可能strcpy之后,get_str才釋放,這樣就避免了我們想要凸顯的問題了,
我們對這個程式進行改進下,
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char* get_str()
{
char str[] = "abcdefg";
return str;
}
int main(void)
{
char* p = NULL;
p = get_str();
//%s:指標指向記憶體區域的內容
printf("p = %s.\n", p);
system("pause");
return 0;
}
運行結果:

畫圖分析:

堆區
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char* get_str()
{
char *tmp = (char*)malloc(100);
if (tmp == NULL)
{
return NULL;
}
strcpy(tmp, "abcdefg");
return tmp;
}
int main(void)
{
char* p = NULL;
p = get_str();
if (p != NULL)
{
//%s:指標指向記憶體區域的內容
printf("p = %s.\n", p);
free(p); // 解除p與堆區的指向關系,告訴作業系統可以讓別人來操作這塊區域了,堆區內容仍然存在,
p = NULL; //雖然解除指向關系之后,但p默認還是會指向堆區的,讓p指向NULL,徹底脫離關系,
}
printf("\n");
system("pause");
return 0;
}
運行結果:

畫圖分析:

函式的呼叫模型

函式呼叫變數傳遞分析
1、fun()和fun2()都在main函式中

2、

3、fun2(a)嵌套在fun1()中,

4、

5、

靜態區域變數的使用
靜態區域變數放在全域區,
堆疊地址的生長方向
堆疊地址的生長方向是向下(遞減)的,

堆地址的生長方向
堆地址的生長方向是向上(遞增)的,
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
int* c = (int*)malloc(4);
int* d = (int*)malloc(4);
printf("c = %d,d = %d\n", c, d);
printf("\n");
system("pause");
return 0;
}

記憶體的存放方向-以陣列為例
給陣列分配一塊記憶體空間,

畫圖示意:

指標強化
強化一
指標也是資料型別
指標也是資料型別,指標變數也是一種變數,

畫圖示意怎么畫,

通過*來操作記憶體
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
int a = 100;
int* p1 = NULL;
//指標指向誰,就把誰的地址賦值給指標
p1 = &a;
//*鑰匙,通過*可以找到指標指向的記憶體區域,操作的是記憶體
*p1 = 22;
// *放在“=”左邊,給記憶體賦值,寫記憶體
// *放在“=”右邊,取記憶體的值,讀記憶體
int b = *p1;
printf(" b = %d\n",b);
printf("\n");
system("pause");
return 0;
}
易錯點
寫記憶體時,一定要確保記憶體可寫
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
//寫記憶體時,一定要確保記憶體可寫
char buf[] = "abcd";
buf[2] = 'a';
printf("buf = %s",buf);
printf("\n");
system("pause");
return 0;
}
字串從字符常量區復制到了buf陣列中,所以修改不會出錯,

直接操作記憶體常量區時,會報錯,
不允許向NULL和未知非法地址拷貝記憶體


強化二
理解指標必須和記憶體四區相結合
怎樣定義一個變數來保存自身的地址?
答:在原來型別基礎上加一個*
int main(void)
{
//一個變數,應該定義一個怎樣型別的指標保存它的地址
//在原來型別基礎上加一個*
int a = 10;
int* p = &a;
int** q = &p;
int******* t = NULL;
int******** u = &t;
printf("\n");
system("pause");
return 0;
}
主調函式和被調函式
主調函式可把堆區、堆疊區、全域資料記憶體地址傳給被呼叫函式,
被調函式只能回傳堆區和全域資料,
(前面 “函式呼叫變數傳遞分析” 也提到過)
記憶體分配方式
指標做函式引數,是有輸入和輸出特性的

輸入特性
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
/* 指標做函式引數 -- 輸入特性 */
//引數串列中寫 /* in */ ,表示有指向的記憶體區域,
//且這個記憶體區域在主調函式中分配,
//這里所指向的記憶體區域就是buf
void fun(char* p /* in */)//指標做函式引數
{
//給p所指向的記憶體區域拷貝
strcpy(p,"abcdefg");
}
void fun2(char* p /* in */)//指標做函式引數
{
if (p == NULL)
{
return;
}
//給p所指向的記憶體區域拷貝
strcpy(p, "abcdefg");
}
int main(void)
{
//輸入,主調函式分配記憶體
char buf[100] = {0};//分配好空間了
fun(buf);//把首地址傳過去
printf("buf = %s\n", buf);
char* str = NULL;
fun2(str);
printf("\n");
system("pause");
return 0;
}
輸出特性
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
/* 指標做函式引數 -- 輸出特性 */
void fun(char** p /* out */, int*len)
{
//先判斷是否為空
if (p == NULL)
{
return;
}
char* tmp = (char*)malloc(100);
if (tmp == NULL)//說明動態陣列開辟失敗
{
return;
}
strcpy(tmp,"abcdefg");
//間接賦值
*p = tmp;
*len = strlen(tmp);
}
int main(void)
{
//輸出,被呼叫函式分配記憶體,必須地址傳遞,否則不能影響實參
char* p = NULL;
int len = 0;
fun(&p,&len);
if (p != NULL)
{
printf("p=%s,len=%d.\n",p,len);
}
printf("\n");
system("pause");
return 0;
}
畫圖分析,很重要,

字串初始化
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
/* C語言沒有字串型別,通過字符資料模擬
C語言字串,以字符'\0'、數字0結尾,
*/
int main(void)
{
//不指定長度,沒有'\0'結束符,有多少個元素就有多長
char buf[] = { 'a','b','c'};
printf("buf = %s\n",buf);
//指定長度,后面沒有賦值的元素,自動補0
char buf1[100] = { 'a','b','c' };
printf("buf1 = %s\n", buf1);
char buf2[100] = { 'a','b','c','0','7' };
printf("buf2 = %s\n", buf2);
char buf3[100] = { 'a','b','c',0 ,'7' };
printf("buf3 = %s\n", buf3);
char buf4[100] = { 'a','b','c','\0' ,'7' };
printf("buf4 = %s\n", buf4);
//使用字串初始化,常用
char buf5[] = "abc";
printf("buf5 = %s\n", buf5);
//strlen:測字串長度,不包含數字0或字符'\0',
//sizeof:測陣列長度,包括數字0或字符'\0',
printf("strlen(buf5) = %d ,sizeof(buf5) = %d\n", strlen(buf5), sizeof(buf5));
char buf6[100] = "abc";
//strlen:測字串長度,不包含數字0或字符'\0',
//sizeof:測陣列長度,包括數字0或字符'\0',
printf("strlen(buf6) = %d ,sizeof(buf6) = %d\n", strlen(buf6), sizeof(buf6));
printf("\n");
system("pause");
return 0;
}
輸出結果:

陣列法和指標法操作字串

自己寫字串拷貝函式

簡化:

簡化

最終的簡潔寫法:

完善字串拷貝函式

必須的:判斷形參指標是否為NULL,
不是必須的:(改變首地址時使用(比如(str++)),移動時不使用(比如(str[begin],bigen變)))最好不要直接使用形參,而是要添加輔助變數,這樣可以在除錯或列印輸出時保證指標指向實參的首地址,
專案開發常用字串應用模型
strstr中的while和do-while模型
利用strstr標準庫函式找出一個字串中substr出現的個數,
do-while模型
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
char* p = "abcdefgabcdegfabcdefgh";
int n = 0;
do
{
p = strstr(p,"abcd");
if (p != NULL)
{
n++;
p += strlen("abcd");
}
else //如果沒有匹配的字串,跳出回圈
{
break;
}
} while (*p != '\0');
printf("n = %d.\n", n);
printf("\n");
system("pause");
return 0;
}
while模型封裝成函式
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int my_strstr(char* str, char* substr,int* n)
{
//輔助變數
int i = 0;
char* tmp_str = str;
char* tmp_substr = substr;
//strstr函式回傳匹配到的字串的首地址
while ((tmp_str = strstr(tmp_str, tmp_substr)) != NULL)
{
//能進來,肯定有匹配的子串
i++;
//重新設定起點位置
tmp_str += strlen(tmp_substr);
if (*tmp_str == 0)
{
break;
}
}
//間接賦值
*n = i;
return 0;
}
int main(void)
{
char* str = "sdabcdefgabcdegfabcd";
char* substr = "abcd";
int n = 0;
int ret = 0;
ret = my_strstr(str, substr ,&n);//通過形參改實參的值,要使用地址傳遞,
if (!ret)
{
printf("n = %d.\n", n);
}
printf("\n");
system("pause");
return 0;
}
兩頭堵模型
求兩頭堵模型下非空字串的個數
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <ctype.h>
/* isspace(測驗字符是否為空格字符)
* 頭檔案 #include <ctype.h>
* 函式int isspqce(int c)
* return:若引數c為空格就回傳TRUE,否則回傳NULL(0),
*/
int my_isspace(char* str, int* n)
{
if(str == NULL || n ==NULL)
{
return -1;
}
int begin = 0;
int end = strlen(str ) - 1;//記得要減一
// 從左開始
// 如果當前字符為空且沒有結束
//或者直接判斷(str [begin] =='')
while (isspace(str [begin]) && str [begin] != 0)
{
begin++;//往右移動
}
while (isspace(str [end]) && str [end] != 0)
{
end--;//往左移動
}
*n = end - begin + 1;
return 0;
}
int main(void)
{
// 兩頭堵,兩頭為空格
char* str = " cdegfabcd ";
int n = 0;
int ret = 0;
ret = my_isspace(str, &n);
if (!ret)
{
printf("n = %d\n", n);
}
printf("\n");
system("pause");
return 0;
}
求兩頭堵模型下非空字串并輸出
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <ctype.h>
/* isspace(測驗字符是否為空格字符)
* 頭檔案 #include <ctype.h>
* 函式int isspqce(int c)
* return:若引數c為空格就回傳TRUE,否則回傳NULL(0),
*/
int my_isspace(char* str, char* buf)
{
if (str == NULL || buf == NULL)
{
return -1;
}
int begin = 0;
int end = strlen(str) - 1;//記得要減一
int n = 0;
// 從左開始
// 如果當前字符為空且沒有結束
//或者直接判斷(str[begin] =='')
while (isspace(str[begin]) && str[begin] != 0)
{
begin++;//往右移動
}
while (isspace(str[end]) && str[end] != 0)
{
end--;//往左移動
}
n = end - begin + 1;
strcpy(buf, str+begin,n);
buf[n] = '\0';
return 0;
}
int main(void)
{
// 兩頭堵,兩頭為空格
char* str = " cdegfabcd ";
char buf[100] = {0};
int n = 0;
int ret = 0;
ret = my_isspace(str, buf);
if (!ret)
{
printf("buf = %s\n", buf);
}
printf("\n");
system("pause");
return 0;
}
const的使用
修飾一個變數為只讀
const修飾的變數,定義時需要初始化,
修飾普通變數為只讀
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
//const :修飾一個變數為只讀
const int a = 10;
a = 10; //err,a由const修飾,不能修改,
}
修飾普通指標變數為只讀
const char *p = buf;
char *const p1 = buf;
const char *const p2 = buf;
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
//指標變數和指標指向的記憶體(中的內容)是兩個概念
char buf[] = "abcdefg";
char buf_b[] = "abcdefg_b";
//從左往右看,跳過資料型別,看修飾哪個字符
//如果是*,說明指標指向的記憶體(中的內容)不能改變
//如果是指標變數,說明指標的指向不能改變,指標的值不能改變
//跳過資料型別char,const修飾的是*,所以指標指向的記憶體(中的內容不能改變)
const char* p = buf; //與 char const* p = buf; 等價
//也就是說 *(p+i)='f' 或者 p[i] ='f';是錯誤的,
//但是指標的指向是可以變的,或者說指標的值可以改變,
//比如:
p = buf_b;
//同樣的,
char* const p1 = buf;
//p1 = buf_b; //錯誤
p1[2] = 'f';
//都不能變
const char* const p2 = buf;
printf("\n");
system("pause");
return 0;
}
修飾結構體變數為只讀
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#if 0
int main(void)
{
//指標變數和指標指向的記憶體(中的內容)是兩個概念
char buf[] = "abcdefg";
char buf_b[] = "abcdefg_b";
//從左往右看,跳過資料型別,看修飾哪個字符
//如果是*,說明指標指向的記憶體(中的內容)不能改變
//如果是指標變數,說明指標的指向不能改變,指標的值不能改變
//跳過資料型別char,const修飾的是*,所以指標指向的記憶體(中的內容不能改變)
const char* p = buf; //與 char const* p = buf; 等價
//也就是說 *(p+i)='f' 或者 p[i] ='f';是錯誤的,
//但是指標的指向是可以變的,或者說指標的值可以改變,
//比如:
p = buf_b;
//同樣的,
char* const p1 = buf;
//p1 = buf_b; //錯誤
p1[2] = 'f';
//都不能變
const char* const p2 = buf;
printf("\n");
system("pause");
return 0;
}
#endif
typedef struct Mystruct
{
int a;
int b;
}Mystruct;
void fun1(Mystruct * p)
{
//指標能變
p = NULL;
//指標指向的記憶體(中的內容)也可以變
p->a = 10;
}
void fun2(Mystruct const *p)
{
//指標能變
p = NULL;
//指標指向的記憶體(中的內容)不可以變
//p->a = 10; err
}
void fun3( Mystruct * const p)
{
//指標不能變
//p = NULL; err
//指標指向的記憶體(中的內容)可以變
p->a = 10;
}
//p只讀
void fun4(const Mystruct * const p)
{
//指標不能變
//p = NULL;
//指標指向的記憶體(中的內容)也不可以變
//p->a = 10;
Mystruct tmp;
tmp.a = p->a;
}
int main(void)
{
printf("\n");
system("pause");
return 0;
}
如何參考另一個c檔案中使用const修飾的變數

在const.c中

在另一個.c中

運行結果:

C語言中const是一個冒牌貨
為什么說它是冒牌的呢?
是因為雖然我們不能直接修改被const修飾的變數的值,但是我們可以通過間接的方式進行修改,
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
const int b = 10;
//b = 100;//erro
int* q = &b;
*q = 100;
printf("b = %d",b);
printf("\n");
system("pause");
return 0;
}
運行結果如下:

二級指標
如果 一個指標變數存放的又是另一個指標變數的地址,則稱這個指標變數為指向指標的指標變數,也稱為二級指標,
二級指標做函式引數—輸出特性
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int getmem(char** p)
{
char *tmp = (char*)malloc(sizeof(char) * 100);
if (tmp == NULL)
{
return -1;
}
strcpy(tmp,"abcdefg");
*p = tmp;
return 0;
}
int main(void)
{
char* p = NULL;
int ret = 0;
ret = getmem(&p);
if (ret == 0)
{
printf("p = %s", p);
}
if (p != NULL)
{
free(p);
p = NULL;
}
printf("\n");
system("pause");
return 0;
}
畫圖分析:

注意:
1、地址傳遞,形參修改會影響到實參,所以要定義一個臨時變數,
2、輸出特性,在被調函式中分配空間,使用malloc后,在被調函式結束后要釋放,
二級指標做函式引數—輸入特性
第一種輸入模型
指標陣列的定義與使用
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
//每個型別都是char*
char *p0 = "0000";
char *p1 = "1111";
char *p2 = "2222";
char *p3 = "3333";
//換成用指標陣串列示,兩種表示,本質一樣
//指標陣列,指標的陣列,他是一個陣列,每一個元素都是指標char *
char* p[] = { "0000" ,"1111" ,"2222" ,"3333" };
for (int i = 0; i < 4; i++)
{
printf("%s\n",p[i]);
}
printf("\n");
system("pause");
return 0;
}
畫圖說明:

改進代碼,變的靈活一些,
特別注意:這種改進是有條件的,需要在定義指標陣列時不指定長度,
也就是char* p[]
而char* p[10]這樣指定長度不行,
//改進代碼,變的靈活一些
int n = sizeof(p) / sizeof(p[0]);
for (int i = 0; i < n; i++)
{
printf("%s\n",p[i]);
}
對指標陣列中的成員進行排序
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
//指標陣列,指標的陣列,他是一個陣列,每一個元素都是指標char *
char* p[] = { "aabb","0000" ,"cccc","1111" ,"2222","4444" ,"3333" };
int n = sizeof(p) / sizeof(p[0]);
printf("陣列中元素的個數:%d\n",n); // 7
int i = 0;
int j = 0;
char* tmp = NULL;
printf("排序前:\n");
for ( i = 0; i <= n-1; i++)
{
printf("%s,",p[i]);
}
//比較排序,按每個字串中的字符的大小來升序排列,
for (i = 0; i < n - 1; i++)
{
for (j = i + 1; j < n; j++)
{
//strcmp是字串比較函式,
//strcmp(a,b)
//字符a>b時,輸出>0,相等時,輸出0,小于時,輸出<0
if (strcmp(p[i], p[j]) > 0)
{
tmp = p[i];
p[i] = p[j];
p[j] = tmp;
}
}
}
printf("\n排序后:\n");
for (i = 0; i <= n - 1; i++)
{
printf("%s,", p[i]);
}
printf("\n");
system("pause");
return 0;
}
需要特別注意的一個地方,

對上述代碼進行函式封裝
這里面有一個過渡非常重要,

因此,
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
// 對于形參,實參是什么型別,形參寫跟它一樣
//void printf_array(char* p[], int n)
void printf_array(char** p, int n)
{
int i = 0;
for (i = 0; i <= n - 1; i++)
{
printf("%s,", p[i]);
}
printf("\n");
}
void sort_array(char** p, int n)
{
int i = 0;
int j = 0;
char* tmp = NULL;
//比較排序,按每個字串中的字符的大小來升序排列,
for (i = 0; i < n - 1; i++)
{
for (j = i + 1; j < n; j++)
{
//strcmp是字串比較函式,
//函式原型:int strcmp(const char *s1,const char *s2);
//引數:s1、s2是指向字串的指標
//回傳值: 自左向右逐個按照ASCII碼值進行比較,直到出現不同的字符或遇’\0’為止,
//如果回傳值 < 0,則表示 s1 小于 s2,
//如果回傳值 > 0,則表示 s1 大于 s2,
//如果回傳值 = 0,則表示 s1 等于 s2,
if (strcmp(p[i], p[j]) > 0)
{
tmp = p[i];
p[i] = p[j];
p[j] = tmp;
}
}
}
}
int main(void)
{
//指標陣列,指標的陣列,他是一個陣列,每一個元素都是指標char *
char* p[] = { "aabb","0000" ,"cccc","1111" ,"2222","4444" ,"3333" };
int n = sizeof(p) / sizeof(p[0]);
printf("排序前:\n");
printf_array(p, n);
sort_array(p, n);
printf("排序后:\n");
printf_array(p, n);
printf("\n");
system("pause");
return 0;
}
第二種記憶體模型
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
//
char a0[5] = "aabb";
char a1[5] = "0000";
char a2[5] = "cccc";
char a3[5] = "1111";
//寫法本質一樣
//二維陣列a[4][5]可以看作是4個a[5]的一維陣列,
char a[4][5] = { "aabb" ,"0000" ,"cccc" ,"1111" };
//a[0] = "aabb";
int i = 0;
for (i = 0; i < 4; i++)
{
//下面三個輸出結果一樣
printf("%s\n", a + i);
printf("%s\n", a[i]);
printf("%s\n",*(a+i));
}
printf("\n");
system("pause");
return 0;
}
概念辨析和畫圖分析:

求二維陣列中一維陣列的個數(行數):
int n = sizeof(a) / sizeof(a[0]);
封裝成函式
前面我們分析了,對于二維陣列名a應該理解成是首行地址,而且a+1應該加一行,如果該行(一維陣列)的長度為n,那么a+1應該加n*sizeof(資料型別)個位元組,比如是char型別,n為5,那么就應該加5,
我們在定義被調函式的引數型別的時候,最最簡單的方式就是主調函式的引數型別和被調引數型別定義一致,
主調函式的變數變數定義為為:
char a[][5] = { "aabb" ,"0000" ,"cccc" ,"1111" };
被調函式的引數定義:
void print_array(char a[][5], int n)
被調函式引數定義絕對不能是下面兩種:
void print_array(char **a, int n)
void print_array(char* a[], int n)
為什么呢?
實際上,這兩種寫法本質上是一回事,都是指標陣列,前面也提到了指標陣列每一個元素都是指標char *,加1相當于加4個位元組,
而我們這里加1應該加n個位元組,所有不可以這樣定義形參,
我們可以驗證下,是不是這樣,
這里被調函式中的實參為:
char a[][5] = { "aabb" ,"0000" ,"cccc" ,"1111" };
a+1相當于加5個位元組,
驗證如下:

加4個位元組

加4個位元組

加5個位元組
這里還有特別重要的一點,在指標陣列中交換的是指標的指向,
而在這里,我們只能交換記憶體塊,
交換的是記憶體塊,而不是指標的指向,
因為這里的a[i],*(a+i)本身都是固定的地址值(都是常量),常量是不能修改的,
所以要使用strcpy來交換記憶體塊,
代碼如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void print_array(char a[][5], int n)
{
int i = 0;
for (i = 0; i < n; i++)
{
//下面三個輸出結果一樣
//printf("%s, ", a + i);
printf("%s, ", a[i]);
// printf("%s, ",*(a+i));
}
printf("\n");
}
void soft_array(char a[][5], int n)
{
int i ,j;
char tmp[5];
for (i = 0; i < n - 1; i++)
{
for (j = i + 1; j < n; j++)
{
if (strcmp(a[i], a[j]) > 0)
{
//交換的是記憶體塊,而非指標的指向
strcpy(tmp, a[i]);
strcpy(a[i], a[j]);
strcpy(a[j], tmp);
}
}
}
}
int main(void)
{
//二維陣列a[4][5]可以看作是4個a[5]的一維陣列,
char a[][5] = { "aabb" ,"0000" ,"cccc" ,"1111" };
int n = sizeof(a) / sizeof(a[0]);
printf("排序前:\n");
print_array(a, n);
soft_array(a, n);
printf("排序后:\n");
print_array(a, n);
printf("\n");
system("pause");
return 0;
}
第三種記憶體模型
匯入
靜態分配一個空間,并拷貝字串,
char p0_sta[100] = { 0 };
strcpy(p0_sta, "abcd");
printf("%s\n", p0_sta);
動態分配一個空間,并拷貝字串,現在不釋放,
char* p0_dyn = NULL;
p0_dyn = (char *)malloc(100);
strcpy(p0_dyn,"abcd");
printf("%s\n", p0_dyn);
動態分配十個上述空間,并拷貝字串,現在不再釋放,
//10個char *,每個值都是空(NULL)
int i = 0;
char* p[3] = {0};
for (i = 0; i < 3; i++)
{
p[i] = (char*)malloc(100);
strcpy(p[i], "abcd");
}
能不能換一種方式(不用for回圈),來分配上述空間呢?
再舉個例子,
(其實這里可以不用舉int的例子,直接用上面的char的例子,但是使用int可以突出開辟空間時所占記憶體大小的問題)
靜態分配a[10]空間大小的記憶體
int a[10];
動態分配一個等價于a[10]空間大小的記憶體
int* q = (int *)malloc(10*sizeof(int));
if (q == NULL)
{
return -1;
}
現在通過類比來模仿上面分配一個等價于char* p[3]空間大小的記憶體,
int n = 3;
char** buf = (char **)malloc(n * sizeof(char*));
if (buf == NULL)
{
return -1;
}
這樣分配好之后,就相當于定義了char *p[3],
畫圖來分析一下:

這樣空間分配好以后,問題又來了,
我們能否直接進行字串拷貝操作:
for (i = 0; i < n; i++)
{
strcpy(buf[i], "abcd");
}
答案是不可以的,我們完成這一步只是相當于
char* buf[3] = {0};
也就是說,p[i] = NULL,而寫記憶體時是不允許向NULL和未知非法地址拷貝記憶體
因此,還需要在堆區開辟空間,需要注意型別,
for (i = 0; i < n; i++)
{
buf[i] = (char*)malloc(100);
strcpy(buf[i], "abcd");
}
畫圖來說明:

最后,還要釋放堆記憶體,先釋放p[i]所指向的堆記憶體,然后再釋放buf所指向的對記憶體,
//先釋放buf[i]所指向的堆記憶體
for (i = 0; i < n; i++)
{
free(buf[i]);
buf[i] = NULL;
}
//再釋放buf所指向的堆記憶體
if (buf != NULL)
{
free(buf);
buf = NULL;
}
封裝函式
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char** getMem(int n)
{
int i;
char** buf = (char**)malloc(n * sizeof(char*));
if (buf == NULL)
{
return NULL;
}
for (i = 0; i < n; i++)
{
buf[i] = (char*)malloc(100);
strcpy(buf[i], "abcdefg");
}
return buf;
}
void print_array(char** buf,int n)
{
int i;
for (i = 0; i < n; i++)
{
printf("%s\n", buf[i]);
}
}
void freeMem(char** buf, int n)
{
int i;
//先釋放buf[i]所指向的堆記憶體
for (i = 0; i < n; i++)
{
free(buf[i]);
buf[i] = NULL;
}
//再釋放buf所指向的堆記憶體
if (buf != NULL)
{
free(buf);
buf = NULL;
}
}
int main(void)
{
int i;
char** buf = NULL;
int n = 3;
buf = getMem(n);
print_array(buf,n);
//解除堆內的指向關系
freeMem(buf, n);
//值傳遞,形參不影響實參
buf = NULL;
printf("\n");
system("pause");
return 0;
}
畫圖分析:

封裝函式改進-多級指標的使用
參考輸出特性,增加臨時指標變數
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int getMem(char*** tmp,int n)
{
int i;
char** buf = (char**)malloc(n * sizeof(char*));
if (buf == NULL)
{
return NULL;
}
for (i = 0; i < n; i++)
{
buf[i] = (char*)malloc(100);
strcpy(buf[i], "abcdefg");
}
*tmp = buf;
return 0;
}
void print_array(char** buf,int n)
{
int i;
for (i = 0; i < n; i++)
{
printf("%s\n", buf[i]);
}
}
int freeMem(char*** tmp, int n)
{
int i;
char** buf = *tmp;
//先釋放buf[i]所指向的堆記憶體
for (i = 0; i < n; i++)
{
free(buf[i]);
buf[i] = NULL;
}
//再釋放buf所指向的堆記憶體
if (buf != NULL)
{
free(buf);
buf = NULL;
}
*tmp = NULL;
return 0;
}
int main(void)
{
int i;
char** buf = NULL;
int n = 3;
getMem(&buf,n);
print_array(buf,n);
//解除堆內的指向關系
freeMem(&buf, n);
printf("\n");
system("pause");
return 0;
}
分配分析:

釋放分析:

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/290699.html
標籤:其他
上一篇:牛客網的編程初學者入門訓練第十五題:按照格式輸入并交換輸出
下一篇:詳解C語言函式篇 + 遞回原理
