c語言數組與指針詳解(上)


徹底搞懂c語言數組與指針

部分引用

  1. c語言指針怎么理解 知乎
  2. 程序設計入門————c語言 (浙江大學翁愷)
  3. 《c primer plus》第六版

基礎知識

1. 指針基礎

基本符號
- &:代表對變量取地址 - int\*或char\*或者把這個星號緊貼着變量比如int \*a = &b: 代表新建一個用來儲存地址的變量,這也代表&b這個值的類型是int\*。 - int \*a, b 或 int\* a, b 中只有a是int指針類型,b是int整型。
  • 關於電腦大小端的討論:大端是指是指數據的高字節,保存在內存的低地址中,而數據的低字節,保存在內存的高地址中。小端是指數據的高字節保存在內存的高地址中,而數據的低字節保存在內在的低地址中。例如下圖:
  • 假設 int b=4; int *a = &b 則*a=4: 因為*a代表a變量中的地址所指的值。重復一下對比:&b是指針類型,值是地址;*b是實際指針b所指的變量的值。
  • 如果打印地址則用%p,以16進制顯示指針的值,而不是用%x,如: printf("%p\n", &i)
  • 32位和64位下指針的長處不同,32位下為4個字節,和int一樣,64位下8個字節。

2. 數組基礎

  • 數組特點:

    • 數組大小一旦定義不可改變
    • 所有的元素具有相同的數據類型
    • 數組中的元素在內存中是連續依次排列的
    • 數組的集成初始化:int a[] = {1,2,3,4,25,6,5,4}; 即讓編譯器自己來數元素的數量
    • 如果這樣賦值:int a[3] = {2}; 則結果是這個數組有三個元素,a[0]=2,a[1]=0,a[2]=0;編譯器自動補全后面的數字為0;
    • 集成初始化的定位:int a[6] = {[1] = 2, [3] = 3, 6}; 結果是 a[0]=0, a[1]=2, a[2]=0, a[3]=3, a[4]=6, a[5]=0; 適合初始數據稀疏的數組。
    • 如果想讓定義的數組變成只讀,即不可修改的類型,則可以在最前面加上一個const。如:const int a[2] = {2, 3, 4}; 當然此條也適用於二維數組。
    • 數組只有在最開始即定義初始化的時候可以集成賦值,下列賦值方法錯誤:int a[3] = {}; a[3] = {1,2,3}; 這時會錯誤,因為這是一個單個元素賦值的方法,況且a[3]已經超出了范圍。
    • 求數組的大小,穩定的方法是 sizeof(a)/sizeof(a[0]) ; 就算修改初始數組a中的數據,也不用修改遍歷時的代碼;
    • 數組作為函數參數時,往往必須再用另一參數來傳入數組的大小。
      • 不能在[]中給出數組的大小
      • 不能在函數中再利用sizeof計算數組的元素的個數
    • 定義數組a, b:int a[10]; b=[]; 則不能直接用b=a來給數組b賦值。
    • 對於數組a,&a=a=&a[0]
  • 二維數組:

    • int a[2][3] 相當於一個2行3列的矩陣
    • int a[0][0] 表示第一行第一列,意味着下標同樣也是從0開始
    • 二維數組的遍歷需要嵌套for循環
    • a[i][j]表示第i行第j列的元素,a[i,j]是一個表達式,相當於a[j],沒有意義,會報錯。
    • 二維數組初始化的時候列數可以省略,行數可以由編譯器來數。例如:inta[][5] = {{0,1,2,3,4},{2,3,4,5,6}};
    • 初始化二維數組的兩種方法:部分初始化則將剩下的那部分賦值為0
      • int a[2][3] = {{5, 6},{7, 8}}; 則a[0][0]=5, a[0][1]=6, a[0][2]=0, a[1][0]=7, a[1][1]=8, a[1][2]=0;
      • int a[2][3] = {5, 6, 7, 8}; 則a[0][0]=5, a[0][1]=6, a[0][2]=7, a[1][0]=8, a[1][1]=0, a[1][2]=0;
    • 三位數組理解方法:比如int box[10][20][30]; 則可以理解成由10個二維數組(每個是20行30列)堆疊起來,這20個數組元素中的每個元素是內含30個元素的數組。

通過程序加深理解一些概念

1. 數組的名字就相當於這個數組第一個元素的內存地址:

#include <stdio.h> 

int main(){
	int a[10]={1,2,3,4,5,6,7,8,9,10};  //定義一個整型數組,這里a實質上是一個指向數組中第一個數據a[0]的指針
	int *p=a;  

	printf("%d\n",*p);
	printf("%d",*(p+1));	
	
	return 0;
} 

返回結果為:
1
2

2. 利用指針對數組進行初始化

#include <stdio.h>

int main(){
	int d[10];
	int *e;
	
	e=&d[0]; //e保存了數組d的第一個數據的地址
	
	for (int i=0; i<10; i++){
		*e = i; //把該地址中的數據依次賦值0,1,2,3,4,5,6,7,8,9 
		e++; //地址累加一次,也就是數組中下一個數據的地址
	} 
	
	for (int i=0; i<10; i++){  
		printf("%d\n", d[i]);  //打印數組d中的所有元素 
	} 
	return 0;
	
} 

3. 數組作為函數參數時,往往必須再用另一參數來傳入數組的大小。

  • 不能在[]中給出數組的大小
  • 不能在函數中再利用sizeof計算數組的元素的個數
  • 聲明數組形參時,下列方法等價:(函數原型可以省略參數名)切記:此為函數聲明時用法,不可直接引用於函數定義
    • int sum(int *ar, int len);
    • int sum(int *, int);
    • int sum(int ar[], int n);
    • int sum(int [], int);
  • 函數定義中不能省略參數名,以下兩種方法可行且等價:
    • int sum(int *ar, int len)
      {
      //省略其他代碼
      }
    • int sum(int ar[], int len)
      {
      //省略其他代碼
      }
#include <stdio.h>
//此方法為最簡單,最基礎的數組遍歷 
int search(int key, int a[], int len)

int main()
{
	int a[]= {1,3,5,2,9,4,12,23,15,32};
	int r = search (12, a, sizeof(a)/sizeof(a[0]));   //傳入參數的時候在main函數中計算好函數的個數傳入到search函數中;另外,此處a傳入的時a[0]元素的地址。
	printf("%d\n", r);
	return 0;
 } 
 
int search(int key, int a[], int len)   //len變量必須要加,因為在search函數中無法用sizeof函數計算數組的大小
{
	int ret = -1;
	int i;
	for (i=0; i<len; i++){
		if (key == a[i]){
			ret = i;
			break;
		}
	}
	return ret;
}

4. 二維數組中數組名的含義

#include<stdio.h>

int main(){
	int a[2][3]={{1,2,3},{4,5,6}};
	
	printf("%p\n",a);   //輸出指針a數據,也就是指針a[0]的地址 
	printf("%p\n",a+1);   //輸出a+1的數據 ,也就是a[1]的地址
	printf("%p\n",&a[0]);
	printf("%p\n",&a[1]);     //驗證上述
	printf("%p\n",(*a)+1);  //輸出的是a[0][1]的地址
	printf("%p\n",&a[0][1]);   //驗證
	printf("%d\n",*(a[0]));  //輸出的是a[0]a[0]的值 
 	printf("%d\n",*(*(a+1)+1));  //輸出的是a[1][1]的值 
}

注意:a是一個2行3列的數值,a+1表示的a[1]值所在的地址,a[1]的值又代表a[1][0]的值所在的地址

5. int與char指針類型的區別

int i=2; int *a=&i 和 char j='m'; char *b=&j 區別在於:int占據四個字節,a中雖然記載的i的第一個字節的地址,但是由於a是int類型的指針,*a讀取的時候自動再往后讀3個字節;而b是char類型的指針,則只讀取當前記錄的這一個字節,自然不能用指針b來保存int i的值。

#include <stdio.h>

int main(){
	int i = 2;
	int j = 's';
	
	int *a = &i;
	char *b = &i;
	
	int *m = &j;
	char *n = &j;
	
	printf("%d\n", *a);
	printf("%d\n", *b);  //會產生warning 
	/* 解釋為什么前兩行輸出為什么一樣:
     * 在存儲中,2作為int存儲為 00000010 00000000 00000000 00000000 四個字節,用此種表示方法是因為我的電腦是個小端電腦(Little-endian)。詳述見下條。
     * a 和 b 所記錄的都是四個字節中第一個字節的地址,*a讀取到的是4個完整的字節,而*b讀取到的是第一個字節 00000010,由於巧合,二者所代表的都是數字1。 
     */

	printf("%c\n", *m);  //會產生warning
	printf("%c\n", *n);
	
	return 0; 
} 

6. 指針內存位置理解深入剖析(一定在自己的電腦上運行試下)

#include <stdio.h>

int main()
{
	int a = 1, b = 2;
	char c = 'c', d = 'd';
	int *m, *n;
	char *j, *k;

	m = &a;
	n = &b;
	j = &c;
	k = &d;
	
	printf("int變量在內存中的存儲情況");
	printf("a  %p\n", m);
	printf("b  %p\n", n); 
	//由於棧自頂向下的存儲方法,內存位置上a與b兩個元素是緊鄰着的,a位置高,b低,相差四個字節。 
	printf("\n");
	printf("對int指針變量+1會得到什么結果,實際改變幾個字節?\n");
	printf("&b+1  %p\n", n+1);
	printf("&a-&b %d\n", m-n);
	//上兩個語句測試得到結果,a與b的地址m,n相差並不是4而是1。因為相差的是存儲單元數而不是字節數 
	printf("\n");
	printf("char指針變量什么情況,本來就相差1個字節?\n");
	printf("c  %p\n", j);
	printf("d  %p\n", k);
	//由於char變量只占1個字節,所以這兩個變量位置地址相差為1 
	printf("\n");
	printf("\n");
	
	printf("int型指針變量占據幾個字節?\n");
	printf("&m  %p\n", &m);
	printf("&n  %p\n", &n);
	printf("char型指針變量占據幾個字節?\n");
	printf("&j  %p\n", &j);
	printf("&k  %p\n", &k); 
	//可以得到,在64位系統上,像m,n這種指針本身的存儲都是占據8個字節,不管是char類型還是int類型。 
	
	return 0;
 } 

一句話概括指針的加減:指針加1,指針的值遞增它所指向類型的大小(以字節位單位),或者說增加一個存儲單元。
short dates; // dates的類型占2個字節
dates + 2 == &dates[2] //相同的地址
*(dates + 2) == dates[2] //相同的值


免責聲明!

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



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