嵌入式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

在这里插入图片描述

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