C语言高级(详解指针)

嵌入式笔记C语言(高级)

  • 调试

    1.printf打印信息,来确定是否执行,或值是否正确

    2.gdb

    执行时,按照自己的需要可以查看到程序执行过程中的任意信息,且能按照我们的需要的方式进行执行

    让程序一步一步进行执行,跟踪程序的过程。比如:可以让程序没有执行的情况下,停留在某条语句,查看比如变量,内存内容,查看到程序到底执行了哪些代码,可以监控到程序的每个执行细节

    调试工具:gdb

    windows:windbg

    macox:lldb

    linux:gdb

gdb:可以调试代码:C、C++、go、object-c、opencl等

gdb -v

sudo apt install gdb

要使用gdb进行调试,有一个前提:在编译程序时,要保存的必要的调试信息(比如第几行是什么代码)

要使用gdb,在编译时 加上选项 -g

gdb调试:

1.打开调试功能

输入:gdb 程序文件名 (编译后的可执行程序)

如:gdb a.out 同时加载a.out程序进行调试

2.退出gdb调试功能:输入:q

3.gdb调试命令

命令作用
b(break)设置断点:在源代码的指定的某一行设置断点(让程序执行到当前的断点位置就暂停执行)
r(run)执行被调试的程序,其会自动执行到第一个断点处(如果没有断点就执行到程序结束位置),就暂停执行(一般用于开始执行,或重新运行)
c(coutinue)当程序在某一个断点暂停时,使用这个命令就继续执行,直到下一个断点或程序结束。
p(print) xxxxxx:变量、表示打印当前这个变量的值。
n(next)另程序执行一行代码即单步执行,执行当前行的代码。
s(step)执行这步的代码,通常也是执行一行,但是如果这一行是函数,则会进入函数执行。
info xx查看信息、例如断点和局部变量:info b info lacals
x x1,x2查看指定内存内容:x1、查看空间的大小、x2内存地址
l(list)查看源代码的内容
watch xxxxxx:变量或者表达式、要监控的变量或者表达式
delete xxxx断点号、删除某个断点

gdb提供了图形化的调试方式TUI、需要curses库的支持

ctrl+x+a

按照gdb命令来操作

设置断点后,如果只是不启用断点disable断点号 启动断点号

指针(重点)

程序的执行都在CPU中执行,如果在磁盘上的程序需要把程序的指令拿到CPU中进行执行。在计算机存在一个存储设备,这个设备用于存储当前执行的程序-----内存指针

指针概念

介绍:

在程序中,定义一个变量(int a =1),将数据1存储到变量a中,这个变量就会存储这个执行程序所占用的内存空间的某个位置。

在程序执行的所有内容都存储在内存中,在内存设计时,每一个字节存储单元,都分配了一个编号,这个编号叫做地址。

在计算机中就是靠变量的地址去访问变量中的数据

存储数据,就需要存储的内存地址

定义一个变量要占用内存空间,内存空间由地址来区别,所以变量名只是表示不同空间--地址

内存空间的编号------地址

1.内存地址

在编写源程序,编译得到二进制可执行程序存储在硬盘中(静态),当程序执行时,就变成动态过程

在程序中表示数据,通过一个变量名来定义变量,在内存中使用空间存储数据,变量名就表示空间的数据

定义变量后,变量存储在内存当中,操作系统将很大的内存区域划分为一个一个小空间(存储单元),每一个小空间为一个字节,即1B=8bit

对每个存储单元通过地址编号来管理,内存的完整空间就是有这一个一个的字节连续组成,又通过地址来区别每个存储单元。

 

32位电脑表示CPU有32位地址线与内存相连。

把变量就是对一块内存空间的表示,泳衣个名字来表示一段空间,相当于就是这段空间的别名

.指针变量

内存地址也是一个数据,那么不也可以用一个变量存储地址这个数据?

指针就是存储另一个变量内存地址的一种数据类型,即指针的内容就是另一个变量的内存地址

指针本身也是一个变量,所以指针也是有自己的地址,但是这个变量有点特殊,存放的是另一个变量的地址

地址没有类型,只有地址中存储的内存数据有类型。

指针2个层此次

  • 首先指针

指针变量的定义:

在定义指针时,存储变量地址,指针存储地址后,怎么知道存储的地址中到底是什么类型数据,如:p=0x10地址,怎么知道0x10地址中的数据是什么类型,所以在定义指针时,最好同时在定义的时候进行初始化。

数据类型* 指针名;

数据类型:是指,这个指针变量指向的数据类型,即指针变量存储地址,由于地址没有类型,指针到底存储的是哪种类型变量的地址

 

*:在定义指针变量时,表示这是定义的指针变量;在或去指针所指向变量值时使用

间接取值符(解引用):

*:获取指针(地址)所指向哪个对应空间的值(按照指向类型),叫做解引用。获取指向空间的值

获取指针变量所指向的空间的数据:

*指针:取指针变量存储的地址,对应空间的值。

以什么样的数据类型来使用:

访问的方式会按照指针的类型去访问

5.指针的运算

指针的运算,对指针变量(指针变量存储的数据(即地址))进行运算

px表示指针变量

+:

px+n:表示指针px想地址增大方向移动n个数据大小

如:

int* p;

p+1=>p+Sizeof(int);

p+5=>p+Sizeof(int)*5

-:px-n:表示指针px想向地址减小方向移动n个数据大小。

px1-px2:指针减指针,指两个地址间间隔多少数据。

如:

double * px1=0x10,px2=0x20;

px2-px1=(0x20-0x10)/sizeof(double)=0x10/b=2

*(乘法):px*n-------错误---指针没有乘法也没有除法。

++:同变量用法相同

-- :同变量用法相同

指针与数组:

数组:

在内存中是一段连续的空间,在这段空间中每个元素占用对应的大小,元素与元素之间相邻的

 

访问数组就可以使用指针

a[0] 和a[1]数据在存储时是连续的,相隔一个数据大小,

指针p=&a[0];

p+1=&a[1];==>*(p+1)=a[1];

数组,只要知道数组的第一个元素的地址(指针)就可以通过地址遍历整个数组。

数组的第一个元素的地址----------数组首地址(数组起始地址)

数组名代表了数组的首地址

数组名+n:移动n个数组元素大小的地址

*(地址):取出地址中的数据(取出数组元素)

例如:

*数组名=========》取出a[0]的地址中的元素,也就是a[0]。

*(数组名+n)可以偏移到数组其他地址访问其他位置变量

指针与字符数组

对于字符数组如果按照整体操作就是字符串操作方式,只需要字符数组首地址,就可以操作一连串字符;

指针:存储地址,指针存储字符数组首地址这个指针可以当做数组名使用。操作字符串,指针就可代表数组名(数组首地址)

char buf[10]
char *p=buf//p代表数组首地址
scanf("%s",p)//输入字符串到buf中

在程序中只要定义常量字符串,则常量字符串表示字符串的首地址

char *p ="常量字符串"

p就是存储了这个常量字符串首地址

注意:

在字符数组中存储字符串时,请在字符串最后一个字符结束之后位置加'\0'

指针与二维数组

二维数组:数组的每一个元素也是一个数组,我们就把这种数组叫做二维数组。

 

二维数组的定义:

一维类型 二维数组名[二维元素个数][一维元素个数]

二维数组中的每一个一维数组,是一组数组的集合

二维数组使用:

1.二维数组的数组元素

二维数组名[下标]-----------:二维数组的数组元素是一维数组

2.二维数组的数组元素,即一维数组;一维数组的数组元素:

二维数组名[下标][一维数组下标]--------:二维数组的数组元素是一维数组,一维数组的数组元素

对二维数组的操作,只能是基本数据类型操作,即二维数组的元素,一维数组,一维数组的元素操作

二维数组初始化:

1.指定二维数组大小

一维数组元素类型 数组名[二维数组元素大小][一维数组元素大小]={

}

例如:

buf2[3][3]={

3,3,3,

4,4,4,

5,5,5

}//其中 buf[0] [0]=3 buf0][1]=3一共九个元素一维数组3个,二维数组3个,没有初始化的值全部为0;

buf2[3][3]={{1,2,3,},{1,3,4},{1,2,4}}

2.不指定二维数组大小

一维数组元素类型 数组名[][一维数组大小]={值}

一维数大小要写

例如:

int buf[] [3]={1,1,2,31,4,14};没有指定二维数组大小,但是每一个原酸是一维数组有3个int类型数据

int buf [] [3]={{},{},{}}

 

二维数组的指针用法:

int a[2][3];

二位数组名,数组名是整个数组的首地址(数组中的第0个元素的地址):a==&a[0],表示第0个元素这个一维数组的地址。

a[0];

是第0个元素,又是一维数组,a[0]是一维数组的数组名,一维数组数组名、表示一维数组首地址。(一维数组中的第0个元素的地址),一维数组中的元素是普通类型(本例中为:int ),即普通成员的地址

a[0]==&a[0][0];

 

当a+1:表示移动二维数组的元素(一维数组)的大小地址

当a[0]+1:表示移动一维数组的元素(普通数据)的大小的地址

数组指针:

是一个指针,存储地址,用于存储整个数组的地址,指针的类型为数组的地址类型,指向整个数组

定义:

数组元素类型 (*指针名)[数组大小];

定义指针变量存储包含10个元素的int类型数组的地址

int (*p)[10];

 

指针数组与多级指针

指针数组:

指针数组是一个数组,里面存放的数组的每一个元素是一个指针变量。

指针指向类型 数组名[数组大小]

指针类型:指针指向类型--------->是一个指针,存储指向类型的地址

如:

int * arr[5];

a数组有5个元素,每个元素都是指针,指针:存储int类型的的地址

多级指针:

指针变量是存储地址IDE,但是由于指针变量本身也要占用空间,所以指针变量也会有地址,如果有另外的指针变量存储的是这个指针变量的地址,把另外的指针变量叫做二级指针。

一级指针变量存普通数据类型的地址,

二级指针变量存一级指针变量的地址,

三级指针变量存二级指针变量的地址。

指针在函数中的使用

函数的形式参数为指针类型

返回类型 函数名(指向类型 *指针名)

参数为指针(地址),可以在函数中修改地址对应空间的值,直接访问对应的内存空间,而参数为变量,传递的变量的值,不会修改到变量。

如果函数的形式参数为数组类型,就会退化为指针类型 ,为地址首地址对应类型的指针

如:

int a[10]---------------->int *p

int b[2][3]------------->int (*P)[3]

int *a[10]----------------->int * s*p

函数的返回值为指针类型

函数1结果为一个数据地址

函数指针

函数在使用是是存储在内存中,所以函数在调用时也是找到函数的地址然后执行函数的内容

函数名就是函数地址

函数指针:

用于存储函数地址类型的指针变量

由于函数的返回值,参数列表的类型都各不相同,函数指针指正存储一种函数的地址

函数指针定义:

返回值类型(* 函数指针名)(参数列表);

函数还真使用:------调用函数指针对用的函数

函数指针名(实际参数);

空指针、野指针、万能指针

野指针:

指针存储的地址不明确(指向的空间不清楚---没有分配不能使用),这个地址定义的空间是否能够在程序中具有操作不清楚。

空指针:

指针记录的地址是0x0这个地址就叫做空指针,系统规定0x0这个地址不允许任何程序进行访问

NULL===0x0

万能指针:

不管是什么类型的指针,指针都是用来存储地址,内存地址用多少位来表示,已经规定好了。

每个地址都是相同的位数

即32位计算机为32bit==4B

64位计算机为64bit==8B

void :空类型 ,不存在这个对用的类型大小

void * :空指针类型,指针类型,大小为4B/8B

void *就叫做万能指针(指向任意类型的地址),可以存储任意类型的地址(指针值),但是不能取*

取对应类型的值。

由于是万能指针,可以存储任意类型的地址,也可以把任意类型的地址赋值给另一个任意类型的

地址

 

关键字

变量定义

  • unsigned

    表示是无符号数据类型(>0),最高位也是数据位

    unsigned 数据类型 变量名;

  • signed

    表示是有符号数据类型,默认添加

    singed 数据类型 变量名;

存储类型关键字

  • auto:创建局部变量时使用,表示自动存储管理(自动分配,自动释放),表示在栈区存储局部变量,如果局部变量不加其他的存储类型,默认添加auto

auto 数据类型 变量名

  • static :静态数据类型,存储在静态区(全局区),

    static 修饰的是局部变量,就表示局部变量不是以栈(auto)方式存储,变量的生命周期不再是语句块结束而结束,二是整个程序结束才结束,作用域不变,还是在语句块中。

    如果遇到多次创建同一个静态变量,不会重复定义初始化,使用之前创建的静态变量

  •  

    第二次循环不会执行static int语句。

    static修饰全局变量:全局变量只能在本文件使用

static 数据类型 变量名;

static 修饰函数:函数只能在本文件内使用

  • register:寄存器存储、表示把数据存储到cpu内部的存储单元中,在执行程序时,入股都能够存储到cpu中就存储进去,在执行过程中,cpu的资源已经占用完了,当做普通的auto 类型存储。

  • register 数据类型 变量名;

  • extern:用于提供一个全局变量的引用,在其他文件中定义了一个变量,现在使用其他文件定义的变量,会把当前的变量名指向一个之前定义过的存储位置。

    extern 数据类型 变量名;

  •  

注意如果全部变量或者静态变量没有进行初始化(由于存储在数据段),自动把没有进行初始化的变量初始化为零,auto局部变量没有进行初始化,则是之前的随机值,不会进行初始化

常量关键字:

  • const :修饰变量 ,则表示该变量不能修改,只能访问(只能获取值,不能修改值)

    const 数据类型 变量名;

    一般使用const 修饰都会完成初始化(定义时赋值);

    const 修饰指针:

    数据类型 * const 指针变量名;

    指针变量不允许修改,不能赋值为其他变量的地址,但是可以修改地址里的值。

    数据类型 const *指针变量名/(const 数据类型 * 指针变量名)

    指针变量允许修改,能赋值为其他变量的地址,但是不可以修改地址里的值。

    数据类型 const *const 指针变量名(const 数据类型 * const 变量名)

    两个都不能修改

    动态内存

    由自己申请内存空间来使用(申请大小由自己决定)存储变量,访问变量;自己进行释放空间的使用方式,叫做动态内存

    动态内存的区域--------堆区---------有由程序员自己管理

    动态内存的申请与释放是通过调用库函数来实现

    #include<stdlib.h>--------对应库函数
    //申请空间函数
    void * malloc(unsigned int size);
    {申请空间:在对空间中申请指定size字节大小的空间用于使用,成功会把申请的空间首地址返回
        return 地址;//本函数返回一个地址
    }
    void free(void *ptr)//释放指定地址ptr这个位置对应的空间#include<stdlib.h>--------对应库函数
    //申请空间函数
    void * malloc(unsigned int size);
    {申请空间:在对空间中申请指定size字节大小的空间用于使用,成功会把申请的空间首地址返回
        return 地址;//本函数返回一个地址
    }
    void free(void *ptr)//释放指定地址ptr这个位置对应的空间

    1. 申请空间

    指针变量 = malloc (字节大小);

    注意:如果你要按照某种类型去使用申请的空间,那你的指针变量就应该是对应的类型,如:按照整型去访问这个空间,指针应该是int 类型的地址

    *指针变量 :就是访问对应空间

    如:

    int* p=malloc(20)

    *p--------访问4个字节,按照整数

    *(p+1)--访问p后的4个字节,按照整数

    例如申请一段空间给int类型的10个数

    int* p=malloc(10*sizeof(int));
    int i = 0;
    if(p==NULL)
    {
        printf("申请失败\n");
    }
    else
    {
            for(i = 0;i < 10;i++)
        {
           *(p+i)=i;
            printf("%d ",*(p+i));
     
        }
        free(p);

    构造类型(自定义类型)

    在使用中有些数据的表示不能够用单一的数据类型就能表示出来,在C语言中就赋予了类型的扩展性,只能按照C语言允许的格式去添加,这种格式就叫做构造类型。

    1.结构体

    设计一种类型,这种类型由多个部分构成,每个部分只表示一个信息(内容),共同组合表示一个完整的信息内容,一个新的类型。

    类型声明:----------说明有一个新类型(不表示有这个类型变量)

    struct 结构体名-------(新的类型名)

    {

    数据类型1 成员名1

    数据类型2 成员名2

    数据类型3 成员名3

    ...

    };

    定义结构体变量:

    struct 结构体名 变量名;

    初始化:

    struct 结构体名 变量名 ={值};

  • 使用结构体变量:

    结构体变量是一个整体,包含多个成员类型

    需要单独对结构体变量中的成员进行操作

    结构体变量名.成员名 -------访问结构体成员

  • memcpy----------拷贝。

    使用结构体指针访问变量内容:

    结构指针:struct 结构体名 * 指针名;

    如:

    struct people * p;

    p=&p1;

    指针访问结构体成员:

    指针名->成员名;p->age;p->name;...

    结构体类型的大小:

    结构体变量的大小由结构体类型决定;

    从0偏移开始存储成员

    偏移位置除以成员大小如果能整出就说明可以存储到这个偏移位置,如果不能整除,则偏移位置移动到能整出位置,进行存储元素。

    结构体的总大小能够整出最大的成员。

    指针名

    2.共用体(联合体)

    与结构体使用方式相同,表示含义也相同,只是共用体是所有的成员共用同一段内存空间。

    定义:

    union 联合体名

    {

    数据类型1 成员名1;

    数据类型2 成员名2;

    ...

    };

    只是成员1,2,3...共用一个空间,每个成员没有单独空间,空间为最大成员的空间

    在使用共用体时,同一时间只能使用一个成员,这个成员使用完,才能使用其他成员

    3.枚举

    有一些类型的数据取值范围只能是某些值,这种数据类型,限制了数据的取值范围,把能够表示的值一一列举出来,这种类型变量就只能从列举的值中取值。

    定义:

    enmu 枚举名

    {

    成员1,

    成员2,

    成员3,

    成员4,

    ......

    };

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

君民

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值