嵌入式linux學習筆記:04_C語言_數組及指針

一、數組

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]   -> 代表該指針指向一個具有3int類型數據的數組

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]

  1. 將第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

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章