C語言 指針【指針 || 陣列 || 字串】

2020-08-09 14:11:38

地址
在C語言中定義的每一個變數都擁有屬於自己的儲存單元的地址,
通常我們認爲一個變數擁有兩個值,一個左值(地址)一個右值(變數的值)
在scanf()中使用到的 取地址符(&),就是向這個地址中輸入數據從而給變數賦值
存取地址 就需要用到指針,用於儲存地址

對於物件的存取

1.直接存取:通過物件名去實現存取,但有作用域的限制
2.間接存取:通過地址去存取,不受作用域影響

指針
用於儲存其他物件的地址值。
在32位元的系統中佔4個位元組,64位元的佔8個位元組

1)指針的定義
	指針變數的定義和其他變數的相同也是: 變數的型別 變數名= 初始值
				int * p = NULL;     指針
		這裏表示定義了一個int  * 型別指針p;他初始化直向空(空指針),
		還有一種定義: int * p ;  這種定義,沒有賦初始值,他是一個(野指針)指向未知,不知道是否能存取,能否修改。通常我們都是先定義空指針。在給他賦值。
		
2)與指針相關的運算:&取地址符  *指向運算子
		在1中定義p 需要賦值(或者申請空間malloc),才能 纔能實現他的作用
		賦值   int  a = 5;
				 p = &a;   //此時將a的地址付給了p,也可以說是p指向了a。
				 此時 *p == 5;
		兩個符號可以約掉  *&p == p     但是&*不能
		
3)指針變數作爲函數的參數
		形參 = 實參的值,
		指針傳入的是地址值,形參和實參指向的是同一個地址,改變形參的值同時會時實參的值發生變化。
		在實現交換兩個數的值時,傳入地址,改變地址對應的值,可以實現交換,,但是注意的是,有題目是交換了傳入的地址,也就是兩個形參的地址做了交換,這樣並不能交換實參的值。

4)陣列與指針
		陣列元素是和普通元素是一樣的,陣列元素也有它自己的地址。
		陣列元素也有左值和右值,只不過陣列的每一個元素的地址是相鄰。
		陣列名 可以代表首元素的地址。
		int a[10];	
		a => &a[0],陣列名a當作指針來看。
		指針加減,看當前的指向,如一維陣列的p=>a =》a[0]    p+1 =>a[1];
		
5)指針常數和常數指針
	指針常數: 指針本身不能改變的,但是指向的空間裏面
		的內容可變。
		如: 陣列名		int * const a;	
	常數指針:是指向常數的指針。
		指向的物件是常數,那麼這個物件不能夠改變的。
		指針本身可以被改變,但是指向的空間的內容不能改變。
		const int * a;
		int const * a;
		
6)陣列名和指針
	陣列名是一個指針常數。
	陣列名可以代表整個陣列,但有些情況下是當作指針來看的。
	如陣列 :   int a[10];
	a <=> &a[0];
	a代表的是a[0]的地址 a+1代表的是a[1]的地址。
	此處的+1移動的是多長需要看當前a代表的是什麼來決定。
	如 : int  a[3][4];
	此時 a  代表的就是&a[0]   a+1 =&a[1]   看成3行4列,可以理解爲。首先指向的是第0行,加1 ,指向的是第1行,其地址在數值上等於他們那一行的第一個元素的地址。
	如:一維陣列
		int a[5] = {1,2,3,4,5};		
		int * ptr = (int *)(&a + 1);    //此時ptr指向了a陣列後面的空間
		printf("%d  %d\n", *(a + 1), *(ptr - 1)); // 2  5
	如:二維陣列
		*(a[1] + 2)       此時 a[1] 應該當作指針來看
		*(&a[1][0] + 2)
		=> *(&a[1][2])
		=> a[1][2]		//!!!!表示第1行第2列的那個元素   (不要寫代表值)
	練習題:
		**a[3][4] 寫出表達式的含義 **
			&a[1][0]: 第1行第0列的元素的地址
			a[2]: (1)代表a[2]這一個陣列 (2)代表第2行第0列元素的地址
			&a[0] + 1: =》 &a[1]  (1)第一行的地址	
			*(&a[1][0] + 1):  代表第1行第1列的那個元素
			
7)指針陣列 與 陣列指針
	a)指針陣列:
		指針陣列是一個數組,不過陣列中存放的全是指針
		定義如:int(任意型別都可以)  * p[10];   相當於一個數組裏面存放了10個int * 型別的數據		
	b)陣列指針:
		陣列指針是一個指針,用於指向一個數組
		定義: int (*p)[10];  指向一個有10個元素的陣列  <=>  int  a[10];
			
8) 字串與指針
	字串就是又一串字元組成 (String型別????C語言中好像還沒涉及到這個型別)
		字串的表達方式有兩種
		 a)字元陣列
		 	char a [6] = {'a','a','a','a','a','\0'};
		 b)字元指針 
		 	char * p = "abcd"	
	字串末尾會預設爲"\0" 表示字串結束
	字串我們只需要儲存他的首地址即可,讀取時會一直讀到0結束
	在C語言裏面字串是儲存在一個叫做 .rodata(只讀數據)的記憶體區域。
	只讀區域無法修改
		char * p = "123456";
		此時執行 *(p+1)=‘a’; 會出現段錯誤,因爲此區域的數據是無法修改的。
	字元陣列
	char a [6] = {'a','a','a','a','a','\0'};
	//與普通的陣列相同,儲存在一個.data/棧空間,陣列區域數據是可讀可寫的
	同時大小也是與陣列相同
	sizeof(a)=6;
	注意!!!使用sizeof時
	char s[] = {'a','b','c','d','e'};// 這種末尾沒有0
		sizeof(s) = 5 		
	char s[] = {"abcde"};    //這種末尾會新增0
		=> char s[] = {'a','b','c','d','e','\0'}; 
		sizeof(s) = 6
	《strlen 計算的值不包括0》
			
9) 字串常用函數
	(1) strlen: 用來求一個字串的長度 
		標頭檔案:	#include <string.h>
		計算字串的長度,不包含'\0'
		l = strlen("abcd\nabc"); == 8  // \n看成一個?? \大多數都是表示跳脫
		l = strlen("123\0123abc\0abc");  == 8   //讀到0結束,但是第一個\0並不是末尾,
		我們碰到這樣的需要判斷他是否爲8進位制的樹,此時\012是當成8進位制來看的。
		\XXX 可接一個、兩個或三個八進制的數
	(2)strcpy/strncpy   字串拷貝
		標頭檔案: #include <string.h>
		strcpy(char *dest, const char *src);
		複製  一個字串到另一個字串,都是從頭開始
		strcpy(沒有考慮越界)strncpy增加了長度限制,
			(1) 遇到\0拷貝結束,此時\0也會被拷貝
			(2) 已經拷貝n個字元了(後面的\0不會自動拷貝,除非第n字元爲\0)
	(3)strcmp/strncmp
		字串比較,同樣strcmpy是全部比較,strncmp是限制比較的長度
		字串比較是一個一個字元比較,
		如果 c1>c2 返回 1,如果c1<c2 返回-1
		如果相同則繼續比較到下一個字元,知道結束,返回0
	(4)strcat / strncat
		源字串空間應該足夠大。將字串鏈接到尾部。
		同樣 拷貝到\0結束,strncpy限制大小