嵌入式C学习第三次任务

一.内存操作

内存操作函数

1. 名称:malloc / calloc

功能:动态内存分配函数

头文件:#include <stdlib.h>

函数原型:void *malloc(size_t size);

void *calloc(int num, size_t size);

其中:num为分配内存块的个数,size为分配内存块的大小

返回值:分配成功返回分配内存块的首地址,失败返回NULL。

区别:(1)malloc一次只能申请一个内存区,calloc一次可以申请多个内存区;

          (2)malloc不会对分配的内存初始化,calloc会初始化为0。

2. 名称:free

功能:动态内存释放函数

头文件:#include <stdlib.h>

函数原型:void free(void *ptr);

ptr为malloc或calloc等内存分配函数返回的内存指针。

返回值:无
 注:malloc和calloc跟free是一一对应的关系,若动态分配内存没有free释放,容易造成内存泄露。

3. 名称:memcpy

功能:拷贝内存空间

头文件:#include <stdlib.h>

函数原型:void *memcpy(void *dest, void *src, size_t n);

其中:dest为目标内存区,src为源内存区,n为需要拷贝的字节数

返回值:指向dest的指针

局限性:未考虑内存重叠情况
函数实现:
 

void *memcpy(void *dest, void *src, size_t n)

{

char *ret = char *dest;

char *dest_t = ret;

char *src_t = char *src;

while(n--)

{

*dest_t++ = *src_t++;

}

return ret;

}

4. 名称:memmove

功能:拷贝(移动)内存空间

头文件:#include <stdlib.h>

函数原型:void *memmove(void *dest, void *src, size_t n);

其中:dest为目标内存区,src为源内存区,n为需要拷贝的字节数

返回值:指向dest的指针

相比memcpy:当dest与src重叠时,仍能正确处理,但是src内容会被改变


函数实现:

  1.  

    void *memmove(void *dest, void *src, size_t n)
    
    {
    
    char *ret = char *dest;
    
    char *dest_t;
    
    char *src_t;
    
    if((unsigned char *dest <= unsigned char *src) || (unsigned char *dest >= unsigned char *src + n))
    
    {
    
    dest_t = char *dest;
    
    src_t = char *src;
    
    while(n--)
    
    {
    
    *dest_t++ = *src_t++; //正向拷贝
    
    }
    
    }
    
    else
    
    {
    
    dest_t = char *dest + n - 1;
    
    src_t = char *src + n - 1;
    
    while(n--)
    
    {
    
    *dest_t-- = *src_t--; //反向拷贝
    
    }
    
    }
    
    return ret;
    
    }

     


5. 名称:memset

功能:初始化指定内存空间

头文件:#include <stdlib.h>

函数原型:void *memset(void *buffer, int c, size_t n);

其中:buffer为分配的内存,c为初始化你内容,n为初始化的字节数

返回值:指向buffer的指针

特别注意:memset是按字节为单位对buffer指向的内存赋值

例:int a[5];

memset(a, 3, 5*sizeof(int)); //错误

上述情况下每个int元素被初始化为:00000011 00000011 00000011 00000011

memset(a, 0, 5*sizeof(int)); //正确,全部初始化为0

memset把buffer所指内存区域的前count个字节设置成某个字符的ASCLL值.一般用于给数组,字符串等类型赋值.

main()

{

    int *p=NULL;

    int i;

    char *q=NULL;

    

    p=(int *)malloc(sizeof(int)*10);

    if(p==NULL)

        exit(1);

    memset(p,0,sizeof(int)*10);

    q=p;

    for(i=0;i<10;i++)

        printf("%d",*(q++));

    free(p);

 执行结果是10个0.
6. 名称:memcmp

功能:比较两个内存空间的字符

头文件:#include <stdlib.h>

函数原型:int memcmp(const void *buf1, const void *buf2, size_t n);

其中:n为要比较的字符数

返回值:当buf1 > buf2时,返回 > 0;当buf1 = buf2时,返回 = 0;当buf1 < buf2时,返回 < 0。

二.指针

 所谓指针,也就是内存的地址;所谓指针变量,也就是保存了内存地址的变量。

 需要注意的是,虽然变量名、函数名、字符串名和数组名在本质上是一样的,它们都是地址的助记符,但在编写代码的过程中,我们认为变量名表示的是数据本身,而函数名字符串名数组名表示的是代码块或数据块的首地址。

1、指针变量的定义和使用

    数据在内存中的地址也称为指针,如果一个变量存储了一份数据的指针,我们就称它为指针变量。

    在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。指针变量的值就是某份数据的地址,这样的一份数据可以是数组、字符串、函数,也可以是另外一个普通变量或者指针变量。

   假设有一个char类型的变量c,它存储了字符'K',并占用了地址为0x11A的内存(地址通常用十六进制表示)。

  另外有一个指针变量p,它的值为0x11A,正好等于变量c的地址,这种情况我们就称p指向了c,或者说p是指向变量c的指针。

  定义指针变量与定义普通变量非常类似,不过要在变量名前面加星号*,格式为:

  1.  

    datatype *name; 或者 datatype *name = value;
    
    其中,*表示这是一个指针变量,datatype表示该指针变量所指向的数据的类型。

     

       *是一个特殊符号,表明一个变量是指针变量,定义指针变量时必须带*。而给指针变量赋值时,因为已经知道了它是一个指针变量,就没必要多此一举再带上*,后边可以像使用普通变量一样来使用指针变量。也就是说,定义指针变量时必须带*,给指针变量赋值时不能带*

       而去指针变量也可以连续定义

定义指针变量时的 * 和使用指针变量时的 * 区别: 

  1.  

int *p = &a;//*用在指针变量的定义中,表明这是一个指针变量,以和普通变量区分开;

*p = 100;//在指针变量前面加*表示获取指针指向的数据


需要注意的是,给指针变量本身赋值时不能加*,如:

int *p;

p = &a;//如果写成*p = &a,报错!!!

*p = 100

 
  1.  

//通过指针交换两个变量的值

#include <stdio.h>

int main(){

int a = 100, b = 999, temp;

int *pa = &a, *pb = &b;

printf("a=%d,b=%d\n", a, b);

//开始交换

temp = *pa;

*pa = *pb;

*pb = temp;

//结束交换


printf("a=%d,b=%d\n", a, b);

return 0;

}

//运行结果:

a=100,b=999

a=999,b=100

    最后, 星号*主要有三种用途:

       ①表示乘法,例如int a = 3, b = 5, c;  c = a * b;,这是最容易理解的。

       ②表示定义一个指针变量,以和普通变量区分开,例如int a = 100;  int *p = &a;

       ③表示获取指针指向的数据,是一种间接操作,例如int a, b, *p = &a;  *p = 100;  b = *p;

二、指针变量运算(加法、减法、比较运算)

       指针变量保存的是地址,而地址本质上是一个整数,所以指针变量可以进行部分运算,例如加法、减法、比较等。但是不能对指针变量进行乘法、除法、取余等其他运算,除了会发生语法错误,也没有实际的含义。

 

三、数组指针(指向数组的指针)

数组(Array)是一系列具有相同类型的数据的集合,每一份数据叫做一个数组元素(Element)。数组中的所有元素在内存中是连续排列的,整个数组占用的是一块内存。以int arr[] = { 99, 15, 100, 888, 252 };为例,该数组在内存中的分布如下图所示:

       定义数组时,要给出数组名和数组长度,数组名可以认为是一个指针,它指向数组的第 0 个元素。在C语言中,我们将第 0 个元素的地址称为数组的首地址。以上面的数组为例,下图是 arr 的指向:

 

如果一个指针指向了数组,我们就称它为数组指针(Array Pointer),定义一个指向数组的指针例子如下:

  1.  

    int arr[] = {23, 56, 4, 34, 45 };
    
    int *p = arr;
    
    arr 本身就是一个指针,可以直接赋值给指针变量 p。arr 是数组第 0 个元素的地址,所以int *p = arr;也可以写作int *p = &arr[0];。也就是说,arr、p、&arr[0] 这三种写法都是等价的,它们都指向数组第 0 个元素,或者说指向数组的开头。

     

       数组指针指向的是数组中的一个具体元素,而不是整个数组,所以数组指针的类型和数组元素的类型有关,上面的例子中,p 指向的数组元素是 int 类型,所以 p 的类型必须也是int *

   引入数组指针后,我们就有两种方案来访问数组元素了,一种是使用下标,另外一种是使用指针。

       (1) 使用下标

       也就是采用 arr[i] 的形式访问数组元素。如果 p 是指向数组 arr 的指针,那么也可以使用 p[i] 来访问数组元素,它等价于 arr[i]。

       (2) 使用指针

       也就是使用 *(p+i) 的形式访问数组元素。另外数组名本身也是指针,也可以使用 *(arr+i) 来访问数组元素,它等价于 *(p+i)。

       不管是数组名还是数组指针,都可以使用上面的两种方式来访问数组元素。不同的是,数组名是常量,它的值不能改变,而数组指针是变量(除非特别指明它是常量),它的值可以任意改变。也就是说,数组名只能指向数组的开头,而数组指针可以先指向数组开头,再指向其他元素。

最后

       (1) 指针变量可以进行加减运算,例如p++p+ip-=i。指针变量的加减运算并不是简单的加上或减去一个整数,而是跟指针指向的数据类型有关。
       (2) 给指针变量赋值时,要将一份数据的地址赋给它,不能直接赋给一个整数,例如int *p = 1000;是没有意义的,使用过程中一般会导致程序崩溃。
       (3) 使用指针变量之前一定要初始化,否则就不能确定指针指向哪里,如果它指向的内存没有使用权限,程序就崩溃了。对于暂时没有指向的指针,建议赋值NULL
       (4) 两个指针变量可以相减。如果两个指针变量指向同一个数组中的某个元素,那么相减的结果就是两个指针之间相差的元素个数。
       (5) 数组也是有类型的,数组名的本意是表示一组类型相同的数据。在定义数组时,或者和 sizeof、& 运算符一起使用时数组名才表示整个数组,表达式中的数组名会被转换为一个指向数组的指针。

三.结构体指针

一个结构体变量的指针就是该结构体变量所占据内存段的起始地址。
可以设一个指针变量,用来指向一个结构体变量,此时该指针变量的值是结构体变量的起始地址。
同时指针变量也可以用来指向结构体数组中的元素。
结构体指针变量说明的一般形式为:
struct 结构名 *结构指针变量名
例如,在前面的例题中定义了stu这个结构,如果要说明一个指向stu的指针变量pstu,可写为

struct stu *pstu;

当然也可以在定义stu结构时同时说明pstu。与前面讨论的各类指针变量相同,结构体指针变量也必须要先赋值才能使用
赋值就是把结构变量首地址赋予该指针变量,不能把结构名赋予该指针变量。
如果boy是被说明为stu类型的结构变量,则:
pstu = &boy;是正确的
pstu = &stu;是错误的
因为结构名和结构变量是两个不同的概念,不能混淆。结构名只能表示一个结构形式,编译系统并不对他们分配内存空间,只有当某变量被说明为这种类型的结构时,才对该变量存储内存空间。
所以以上 pstu = &stu;是错误的,不可能去取一个结构名的首地址。有了结构指针变量,就更方便地访问结构变量的各个成员。
其访问的一般形式为:
(*结构指针变量).成员名
或为:
结构指针变量->成员名
例如:(*pstu).num
或者:pstu->num

案例:

struct stu{
    int num;
    char *name;
    char sex;
    float score;
}boy1 = {006,"zhangzhang",'1',69.6};

int main() {

    struct stu *pstu;
    pstu = &boy1;

    printf("%d,%s\n", boy1.num, boy1.name);

    printf("%d,%s\n", (*pstu).num, (*pstu).name);

    printf("%d,%s\n", pstu->num, pstu->name);

}

关于结构体指针的传递和调用的用法:

 

上述调用中采用的结构体变量。在传入函数时通过指针void *para指针传递过去。需要注意的是不能直接使用para->a来访问结构体的成员。这是因为para只是接收过来的地址。para虽然指向的结构体的首地址。但是这个指针并不知道自己指向的是什么内容和有多少成员。需要(date *)para强制转化一下。这样para就可以知道自己是什么类型的指针,有多少成员。

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值