數組及指針
一、數組
1、數組的概念?
1、 什麼是數組?數組與普通變量有什麼關係?
數組其實是集合來的,它是由多個相同類型的普通變量組合而成。當用戶需要同時定義多個相同變量時,就可以使用數組。
2、 定義數組時,需要交代什麼東西?
1)數組元素的個數?
2)數組中每一個元素的數據類型? ->char
short
int
long
float
double
定義公式:
數據類型 數組名字[元素的個數]
例子:定義一個具有100個int類型變量的數組
int A[100];
3、從內存空間分析數組特點:
int main()
{
int a; //在內存空間中連續申請4個字節,使用變量a間接訪問這片空間。
int A[100]; //在內存空間中連續申請400個字節,使用變量A間接訪問這片空間。
printf("sizeof(a) = %d\n",sizeof(a));//4
printf("sizeof(a) = %d\n",sizeof(A));//400
return 0;
}
4、定義了數組,編譯器如何處理數組?
例如: int A[100]
其實分開兩個部分進行處理, “int”爲第二部分, "A[100]"作爲第一部分。
第一部分 -> 決定內存空間中元素的個數。
第二部分 -> 決定每一個元素的數據類型
2、數組初始化
1)定義同時初始化
-> 元素數據類型 數組名字[元素個數] = {初始化列表,每一個成員之間使用","分開}
int A[3] = {100,200,300}; //編譯通過
int A[3] = {100,200}; //編譯通過
第三個沒有賦值的成員等於0
int A[3] = {100,200,300,400}; //編譯警告
警告: warning: excess elements in array initializer
int A[] = {100,200}; //編譯通過 決定了下標等於2
int A[] = {100,200,300}; //編譯通過 決定了下標等於3
int A[] = {100,200,300,400}; //編譯通過 決定了下標等於4
2)先定義,沒有初始化
int a; -> 局部變量 -> 隨機值
int a; -> 全局變量 -> 0
int A[3]; -> 局部變量 -> 全部元素都是隨機值
int A[3]; -> 全局變量 -> 全部元素都是0
int A[3] = {100,200}; -> 局部變量 -> 最後一個元素是0
int A[3] = {100,200}; -> 全局變量 -> 最後一個元素是0
3)先定義,後初始化 -> 一般結合循環來完成!
int A[3];
A = {100,200,300}; //編譯出錯
int A[3];
A[3] = {100,200,300}; //編譯出錯
小結論:定義數組時沒有整體初始化,則之後都不能整體初始化,只能單個初始化。
int A[3];
A[0] = 100;
A[1] = 200;
A[2] = 300;
3、數組的下標
int A[3]; -> 在內存空間中連續申請12個字節,使用變量A間接訪問這片內存空間,在訪問數組中,使用下標來對成員進行訪問。
例如:
int A[N] -> 下標範圍: 0~N-1
記住: 最後一個元素是A[N-1],而不是A[N]
4、研究數組的名字含義
1)當數組名作用於sizeof()時,數組名代表這個數組的內存空間。
sizeof() -> 計算內存空間的大小
例子:
int main()
{
int A[3];
printf("%d\n",sizeof(A)); //12
}
2)當數組名不作用於sizeof()時,數組名代表數組首元素的地址。
int A[3];
數組名: A
首元素: A[0]
首元素的地址: &A[0]
結論: A = &A[0]
例子:
int main()
{
int A[3] = {100,200,300};
printf("A[0] = %d\n",A[0]);
printf("&A[0] = %p\n",&A[0]); //0xbfec8cb4
printf("A = %p\n",A); //0xbfec8cb4
return 0;
}
sizeof(粵嵌) -> 計算整個粵嵌的大小
粵嵌 -> 僅僅代表整個粵嵌第一層樓的地址
二、指針
1、指針的概念
1、什麼是指針?什麼是指針變量?指針幹什麼用?
指針 指向是內存上地址,例如: 0xbfec8cb4
指針變量指向是專門用於存放地址的變量 p
指針是唯一的地址,所以確定申請的內存空間在哪裏。
例子:
int a; -> 在內存申請一片內存空間,使用變量a間接訪問這片內存
&a; -> 獲取a變量的地址 &-> 取址符
2、究竟&a獲取到地址,存放在哪裏?
指針是一個地址,地址就應該存在指針變量中。
3、指針變量如何定義?
指針變量怎樣定義取決於指向的內容的數據類型。
例如: int -> int *p
char -> char *p
double -> double *p
定義步驟:
1)先寫一個 *
2)在*後面寫一個指針變量名 *p
3)確定指向的內容 int a;
4)把第3步的內容的變量名去掉 int
5)把第4步的結果寫在第2步結果前面 int *p
結果:int *p -> 指針變量,該變量指向一個整型數據!
變量定義公式: 數據類型 + 變量名
變量名:p
數據類型: int*
例子:
int a=100; -> 在內存申請一片內存空間,使用變量a間接訪問這片內存
&a; -> 獲取a變量的地址 &-> 取址符
int *p = &a; -> 將a變量的地址賦值給指針變量p
4、已知指針變量的值,如何求出該地址指向的內容是什麼?
取地址: 已知變量值a,求地址值。 &a
解引用: 已知地址值p,求變量值。 *p
例子:
int a = 100; -> 在內存申請一片內存空間,使用變量a間接訪問這片內存
&a; -> 獲取a變量的地址 &-> 取址符
int *p = &a; -> 將a變量的地址賦值給指針變量p
int b = *p; -> 解引用出p地址指向的值,再賦值給變量b
例子====
#include <stdio.h>
int main()
{
int a = 100;
int *p = &a;
printf("a = %d\n",a); //100
printf("&a = %p\n",&a); // 0xbfde90c8
printf("p = %p\n",p); // 0xbfde90c8
printf("*p = %d\n",*p); //100
return 0;
}
=======================================================
練習1: char b=‘A’ 能不能賦值給int *p ? -> 不可以,指針類型不對,只能是char *
char b = ‘A’;
char *p = &b;
printf("*p = %c\n",*p); // ‘A’
printf("*p = %d\n",*p); // 65
5、指針的內存空間多大?
指針是一個地址,在linux系統32位中,地址長度都是4字節,所有指針變量都是4字節。
sizeof(指針變量名) = 4
#include <stdio.h>
int main()
{
int a = 100;
char b = 'A';
int *pa = &a;
char *pb = &b;
printf("%d\n",sizeof(a));//4
printf("%d\n",sizeof(b));//1
printf("%d\n",sizeof(pa));//4
printf("%d\n",sizeof(pb));//4
return 0;
}
2、野指針與空指針
1、什麼是野指針?
定義了一個指針變量之後,但是沒有進行初始化,指針變量就會賦值一個隨機值,指向一個未知區域。
int a; -> 隨機值
a = 5;
int *p; -> 隨機值 -> 這時候p就稱之爲野指針。
p = &a;
2、如何解決野指針?
1)在定義指針初始化指針指向的區域。
例子:
int a;
int *p = &a;
2)使用空指針 -> NULL
NULL其實是一個宏定義,真正NULL是等於0,被定義在一個頭文件:
#if defined(__cplusplus)
#define NULL 0
#else
#define NULL ((void *)0)
#endif
例子:
int *p = NULL;
3)其實空指針只是安全區域中其中一個地址,安全區域地址範圍: 0x00000000~0x08048000,只要指針指向該範圍,那麼指針就不會指向別的區域了。
int a;
int *p = (0x00000000~0x08048000);
p = &a;
3、如果訪問了安全區域的數據,會出現什麼情況?
例子:
#include <stdio.h>
int main(int argc,char *argv[])
{
int *p = (int *)0x08047000;
int a = *p;
printf("p = %p\n",p);
return 0;
}
編譯: 通過
執行: 出現段錯誤 Segmentation fault (core dumped) -> 非法內存訪問。
1、如何在程序中尋找段錯誤?
段錯誤不是語法錯誤,所以在編譯時不會提示出錯,只有等到運行時纔會提示出現段錯誤,但是段錯誤不會提示在哪一行,可以通過printf()函數來尋找段錯誤位置,只要發生段錯誤,那麼程序就會馬上結束。
例子:
printf(“11111!\n”);
xxxx;
printf(“22222!\n”);
yyyy;
printf(“33333!\n”);
zzzz;
執行:
11111!
22222!
Segmentation fault (core dumped) -> 說明段錯誤是出現"yyyy;"
2、找到段錯誤之後,怎麼處理?
一般段錯誤都是與指針指向有關,找到段錯誤準確那一行就打印對應指針的值。
4、void* 通用類型的指針?
1)void *指向一個什麼類型的數據?是指向一個void型變量嗎?
例子:
void a; -> 沒有void型變量
void *p = &a;
void b = *p;
void *類型指針代表該指針指向任何類型,而且C語言沒有void型變量。
2)把特定類型賦值給void *代表含義?
int a; --> 買了一個商場(int)
int p = &a; --> p裝者商場的地址 p的數據類型是 商場(int*)
void *pa = p; --> p的地址賦值給pa,pa是void *類型,pa可以指向任何類型的數據
含義:本身p已知指向int類型,賦值給pa,pa還是指向那片區域,但是那片區域是什麼類型的數據,就不知道了!
3)把void *賦值給特定類型含義?
int a; --> 買了一個商場(int)
int p = &a; --> p裝者商場的地址 p的數據類型是 商場(int*)
void *pa = p; --> p的地址賦值給pa,pa是void *類型,pa可以指向任何類型的數據
int pb = pa; --> pa指向未知類型,但是賦值給pb之後,由於pb是int,所以該地址上數據變量int *
4)解引用
可以解引用特定的類型,不可以解引用void *,如果需要解引用void *,必須先將void *轉換到特定的數據類型,再解引用。
例子:
int a = 100;
int *p = &a;
printf("%d\n",*p); //100
void *pa = p;
printf("%d\n",*pa); //編譯出錯
void *pa = p;
printf("%d\n",*(int *)pa); //100
void *pa = p;
int *pb = pa; 結論: 把void *賦值給某種特定的類型,其實就是強轉爲該種類型。
printf("%d\n",*pb); //100
5、指針的運算
例子:指針加法
int a;
int *pa = &a;
pa+1 -> 向上移動1個單位,每一個單位等於多少個字節,就要看這個指向的內容佔用多少個字節。
char b;
char *pb = &b;
pb+2 -> 向上移動2個單位,每一個單位等於1字節,一共是2個字節。
例子:指針減法
int c;
int *pc = &c;
pc-pb //編譯出錯,不同類型之間指針不可以相減。
pc-pa //編譯通過,可以求出兩個指針之間相差單位數。
6、數組的運算
1、由於數組的名字就是數組首元素地址,一旦申請空間後,該數組的地址就不能改變,所以數組名字是一個常量
#include <stdio.h>
int main()
{
int A[3];
printf("A = %p\n",A); //0xbfe396c4
A = 0xbfe10104; -> 數組名是一個常量,不能再賦值別的地址給數組名。
A[0] = 100;
A[0] = 200; -> 數組本身是一個變量,可以隨時對成員賦值。
return 0;
}
2、數組名字是首元素地址,那麼首元素的地址是什麼類型?
int A[3] -> 每一個成員都是int類型的 -> 首元素也是int類型 -> 首元素的地址就是int *
例子:
int A[3] = {100,200,300};
A -> 首元素的地址
A+1 -> 將首元素的地址往上移動1個單位,由於首元素是int類型,往上移動4個字節。
A+2 -> 將首元素的地址往上移動2個單位,由於首元素是int類型,往上移動8個字節
*(A+0) -> 解引用首元素的地址 -> 首元素 -> A[0]
*(A+1) -> 解引用首元素往上移動1個單位的地址 -> 第二個元素 -> A[1]
*(A+2) -> 解引用首元素往上移動2個單位的地址 -> 第三個元素 -> A[2]
結論:數組下標其實首元素往上移動的單位數
A[n] = *(A+n) 非常非常非常重要!
3、加法交換律
A[n] = *(A+n) = *(n+A) = n[A]
#include <stdio.h>
int main()
{
int A[3] = {100,200,300};
printf("A[0] = %d\n",A[0]); //100
printf("*(A+0) = %d\n",*(A+0)); //100
printf("*(0+A) = %d\n",*(0+A)); //100
printf("0[A] = %d\n",0[A]); //100
return 0;
}
7、複雜指針定義?
1、定義:
簡單指針 -> 指針指向基本的數據類型,例如: int* char* double*
複雜指針 -> 指針指向非基本數據類型,例如: 指針/數組/函數/結構體
2、指向指針變量的指針 -> 二級指針?
int a = 100;
int *pa = &a;
int **p = &pa;
例子:
#include <stdio.h>
int main()
{
int a = 100;
int *pa = &a;
int **p = &pa;
printf("pa = %p\n",pa);
printf("p = %p\n",p);
printf("%p\n",*p);
printf("%d\n",**p);
return 0;
}
練習:
1)設有定義:int a,*pa=&a;以下scanf語句中能正確爲變量a讀入數據的是( A ) scanf("%d",&a); pa = &a
A) scanf(“%d”,pa); B) scanf(“%d”,a);
C) scanf(“%d”,&pa); D) scanf(“%d”,*pa);
2)若有以下定義和語句
#include <stdio.h>
int a=4,b=3,*p,*q,*w;
p=&a;q=&b;w=p;q=NULL;
則以下選項中錯誤的語句是( A )
A) *q=0; B) w=p; C)*p=a; D) *p=*w;
3)有以下程序
main()
{
int a=7,b=8,*p,*q,*r;
p=&a;q=&b;
r=p;p=q;q=r;
printf(“%d,%d,%d,%d\n”,*p,*q,a,b);
}
程序運行以後的輸出結果是( C )
A)8,7,8,7 B) 7,8,7,8
C)8,7,7,8 D) 7,8,8,7
4)程序中對fun函數有如下說明
void *fun();
此說明的含義是( C )
A) fun函數無返回值
B) fun函數的返回值可以是任意的數據類型
C) fun函數的返回值是無值型的指針類型
D) 指針fun指向一個函數,該函數無返回值
5)有以下程序
int *f(int *x,int *y)
{
if(*x<*y) return x;
else return y;
}
main()
{
int a=7,b=8,*p,*q,*r;
p=&a;q=&b;
r=f(p,q);
printf(“%d,%d,%d\n”,*p,*q,*r);
}
執行後輸出結果是( B )
A) 7,8,8 B) 7,8,7 C) 8,7,7 D) 8,7,8
小結:
**複雜指針定義**
1、簡單指針 -> 指向基本數據類型 char int long..
複雜指針 -> 指向非基本數據類型 指針/數組/函數/結構體
2、二級指針?
指向指針的指針。
int a;
int *pa = &a; pa就是一級指針
int **p = &pa; p就是二級指針
3、數組指針?
指向整個數組的基地址指針就是“數組指針”
1)如何定義數組指針?
方法與定義簡單指針一致。
int a;
int *p = NULL;
p = &a; -> 整型指針 -> 指針類型:int* -> 代表該指針指向一個整型數據。
int A[3];
int(*p)[3] = &A; -> 數組指針 -> 指針類型:int(*)[3] -> 代表該指針指向一個具有3個int類型數據的數組
2)數組中&A與A有什麼區別?
&A
-> 代表這個數組的地址 int(*)[3]
A
-> 代表這個數組首元素的地址 int *
int A[3];
int(*p)[3]=A;
-> 不能把int類型數據賦值給 int()[3],因爲類型不匹配!
3)解引用
int A[3] = {100,200,300};
int(*p)[3] = NULL;
p=&A;
請問*p得到什麼?
*p = *(&A) = A = &A[0] -> 解引用數組指針,得到數組首元素的地址。
請問p[0]/p[1]是什麼?
`p[0] = *(p+0) = *(p) = *(&A) = A = &A[0]
p[1] = *(p+1) p+1已經越界,訪問未知區域。
請問(*p)[1]得到什麼?
(*p)[1] = (*&A)[1] = A[1] = 200
練習1:若有以下說明:
int B[10];
int (*p)[10]=&B;
int *px=B;
則下列能正確引用數組的元素的是?
A. *p+1 B.*(p[1]) C.(px+3)[2] D.(*px)[3] E. (*p)[0] F. *(px+1)
4、函數指針?
指向函數的一個指針。
1)如何定義函數指針?
例子:
int a;
int *p = &a;
int fun(int x,int y); -> 指向該函數指針怎麼寫?
int(*p)(int,int) = &fun;
結果:int(*p)(int,int)
變量名:p
數據類型:int(*)(int,int)
2)在linux C語言中,函數名字就是函數的地址,所以: fun 等價於 &fun
int fun(int x,int y);
int(*p)(int,int) = &fun
; 等價於 int(*p)(int,int) = fun;
練習2:求出兩個數字最大值,要求使用函數指針完成。
#include <stdio.h>
int fun(int x,int y)
{
int z;
z = (x > y ? x : y);
return z;
}
int main()
{
int max;
int(*p)(int,int) = NULL;
p = &fun;
max = p(100,200);
printf("max = %d\n",max);
return 0;
}
8、數組作爲函數的參數時,在內存中的變化情況?
1、學習過傳遞類型
例子1:傳遞int類型數
void fun(int x) //x = a;
fun(a);
例子2:傳遞指針地址
void fun(int *x) x = &a;
fun(&a);
例子3:傳遞函數
void fun(void (*x)(int)) x = &myfun / x = my_fun
void my_fun(int a);
fun(my_fun);
2、數組作爲實參時,其實將數組首元素傳遞過去。
例子:
以下三種寫法完全等價
void fun(int x[]) // x = A = &A[0]
void fun(int x[3]) // x = A = &A[0]
void fun(int *x) // x = A = &A[0]
int main()
{
int A[3];
fun(A); //在這裏,A不是作用sizeof(),所以代表首元素地址 int*
//等價於fun(&A[0]);
}
練習:
eg、有以下程序
void swap1(int c0[], int c1[])
{
int t ;
t=c0[0]; c0[0]=c1[0]; c1[0]=t;
}
void swap2(int *c0, int *c1)
{
int t;
t=*c0; *c0=*c1; *c1=t;
}
main()
{
int a[2]={3,5}, b[2]={3,5};
swap1(a, a+1);
swap2(&b[0], &b[1]);
printf(“%d %d %d %d\n”,a[0],a[1],b[0],b[1]);
}
程序運行後的輸出結果是( D )
A)3 5 5 3 B)5 3 3 5
C)3 5 3 5 D)5 3 5 3
eg、有以下程序
void fun(int *a,int i,int j)
{
int t;
if(i<j)
{
t=a[i];
a[i]=a[j];
a[j]=t;
fun(a,++i,--j);
}
}
main()
{
int a[]={1,2,3,4,5,6},i;
fun(a,0,5);
for(i=0;i<6;i++)
printf(“%d”,a[i]);
}
執行後的輸出結果是( A )
A) 6 5 4 3 2 1 B) 4 3 2 1 5 6
C) 4 5 6 1 2 3 D) 1 2 3 4 5 6
eg、數組作爲函數的返回值時,情況是如何的?
#include <stdio.h>
char *fun()
{
char A[4] = {'a','b','c'};
printf("A = %p\n",A);
printf("%s\n",A);
return A; //-> 返回數組首元素的地址 類型: char*
}
int main()
{
char *p = NULL;
p = fun();
printf("p = %p\n",p);
printf("%s\n",p);
}
編譯警告:warning: function returns address of local variable -> 數組A再函數fun結束時,內存空間會釋放。
解決方案:
1)將數組設置爲全局變量
2)在數組前面加static修飾數組 -> 數組被static修飾後,不再存在與棧區,而是存在數據段。
9、二維數組
1、二維數組的基本概念
二維數組在內存中是線性規則,不存在行與列的關係,其實二維數組就是一個一維數組,只是一維數組中每一個成員都是數組。
例子:
整型數組: int A[5]
-> 該數組中每一個成員都是int類型數據。
二維數組:數組 A[5]
-> 該數組中每一個成員都是數組
2、如何定義二維數組?
只需要確定數組的元素個數與每一個元素的數據類型即可!
定義數組的方式:
例子1:定義具有5個int類型數據的數組:
1)給一個數組名 A
2)確定數組中元素的個數,使用[]括住它,跟在數組名後面A[5]
3)確定數組中每一個成員的數據類型是什麼 int a
;
4)將第3步結果的變量名去掉 int
5)將第4步結果寫在第2步結果前面 int A[5]
例子2:定義二維數組
1)給一個數組名B
2)確定數組中元素的個數,使用[]括住它,跟在數組名後面 B[2]
3)確定數組中每一個成員的數據類型是什麼 int A[3]
4)將第3步結果的變量名去掉 int [3]
- 將第4步結果結合到第2步結果中
int B[2][3]
結果:int B[2][3]
代表這個數組中有兩個成員,每一個成員都是具有3個int類型數據的數組。
3、二維數組的賦值
int B[2][3] = {{1,2,3},{4,5,6}};
4、二維數組的數組名
例子: int B[2][3]
sizeof(B) = 24
&B
-> 代表整個二維數組的地址, 類型:int(*)[2][3]
B=&B[0]
-> 代表二維數組的首元素的地址, 類型:int(*)[3]
B[0]=&B[0][0]
-> 代表二維數組的首元素的首元素的地址, 類型: int *
B[0][0]
-> 代表二維數組的首元素的首元素的值, 類型:int
5、解引用
1)請問解引用二維數組的名字得到什麼?
得到二維數組的首元素的首元素的地址。
int B[2][3] = {{1,2,3},{4,5,6}};
*B=*(&B[0])=B[0]=&B[0][0]
2)請問解引用二維數組的首元素得到什麼?
得到二維數組的首元素的首元素的值。
*B[0]=*(&B[0][0])=B[0][0]
練習2:
eg、若有以下說明和語句:int c[4][5],(*p)[5];p=c;
能正確引用c數組元素的是( D )
A) p+1 B) *(p+3) C) *(p+1)+3 D) *(p[0]+2)
eg、有以下程序
main()
{
int a[3][3],*p,i;
p=&a[0][0];
for(i=0;i<9;i++)
p[i]=i+1;
printf(“%d\n”,a[1][2]);
}
程序執行以後的輸出結果是( B )
A) 3 B) 6 C) 9 D) 7