主頁 > 軟體設計 > C語言提高(上)

C語言提高(上)

2021-07-29 07:54:13 軟體設計

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語言函式篇 + 遞回原理

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more