一、内存的使用

1.1 你创建的内存区域可能是脏的

  当我们创建一个内存区域的时候,内存中的数据可能是乱七八糟的(可能是其他代码用过后遗留的数据),如下面一段代码:

int main(int argc, char *argv[])
{// 下面申请的20个字节的内存有可能被别人用过char chs[20];// 这个代码打印出来的可能就是乱码,因为printf的%s是“打印一直遇到'\0'”。printf("%s\n",chs);return 0;
}

  其运行结果是如下图所示的乱码,因为printf的%s是“打印一直遇到'\0'”。

1.2 解决脏内存区域的办法

  那么,如何解决上面我们有可能会访问的脏内存区域呢?在C语言中,可以采用如下的两种方法:

  (1)使用memset函数首先清理一下内存:

void *memset(void *s, int ch, unsigned n);

将s所指向的某一块内存中的每个字节的内容全部设置为ch指定的ASCII值,

块的大小由第三个参数指定,这个函数通常为新申请的内存做初始化工作,

其返回值为指向S的指针。

  那么,我们可以使用memset函数来清理内存,即填充创建的这块内存区域:

int main(int argc, char *argv[])
{// 下面申请的20个字节的内存有可能被别人用过char chs[20];// 这个代码打印出来的可能就是乱码,因为printf的%s是“打印一直遇到'\0'”。printf("%s\n",chs);// memset内存初始化:memset(void *,要填充的数据,要填充的字节个数)memset(chs,0,sizeof(chs));int i,len=sizeof(chs)/sizeof(char);for(i=0;i<len;i++){printf("%d | ",chs[i]);}return 0;
}

  可以看到该代码的运行结果如下图所示,将20个字节都置为了0。

  通常对于在堆空间中创建的内存区域,一般都会用到memset函数来清理。

  (2)使用初始化填充0:

   除了使用memset函数之外,另一种比较直接的方式就是在初始化时直接指定要填充的数据,如下面的代码:

int main(int argc, char *argv[])
{int i;char chs[20] = { 0 };for(i=0;i<sizeof(chs)/sizeof(char);i++){printf("%d | ",chs[i]);}printf("\n");int nums[10] = { 7, 5 };for(i=0;i<sizeof(nums)/sizeof(int);i++){printf("%d | ",nums[i]);}return 0;
}

  可以看到运行结果:对于int数组,如果不为其进行初始化,会默认填充0到内存区域。

二、结构体的使用

2.1 结构体的初始化

  结构体其实就是一大块内存,我们可以对它进行格式化的存储和读取。如下代码所示,我们试着定义一个结构体:

struct _Person
{char *name; int age; double height;
};int main(int argc, char *argv[])
{struct _Person p1;// 不初始化内存区域是脏的printf("p1.age is %d\n",p1.age);return 0;
}

  在main函数中,我们声明了一个刚刚定义的结构体p1,但是我们并没有进行初始化。这时,会出现上面所提到的脏内存区域的问题,如下所示:

  如何解决呢,还是采用上面所说的两个办法:

  (1)memset:

    // 方法一:使用memset进行清理memset(&p1,0,sizeof(struct _Person));printf("p1.age is %d\n",p1.age);p1.name = "周旭龙";p1.age = 26;printf("Name : %s , Age : %d\n",p1.name,p1.age);printf("p1.age is %d\n",p1.age);printf("------------------------------\n");

  注意这里第三个参数是 sizeof(struct _Person)

  (2)初始化为0:

    // 方法二:初始化struct _Person p2 = { 0 };p2.name = "刘德华";p2.age = 60;printf("Name : %s , Age : %d\n",p2.name,p2.age);

  两块代码运行结果如下图所示:

  第一行是未经清理的脏内存数据,第二部分是使用memset进行清理后再赋值的结果,第三部分是直接初始化后再赋值的结果。

2.2 包含指针的结构体大小

  对于普通数据类型的结构体,计算结构体的的大小是件容易的事。但是,如果是有包含有指针的结构体呢?我想,很多跟我一样的菜鸟都会犯错。那么,我们来看看刚刚那个结构体的大小是多少吧?

struct _Person
{char *name; // 指针为4个字节,地址(int)int age; // 4个字节double height; // 8个字节
};int main(int argc, char *argv[])
{struct _Person p1;printf("The size of p1 is %d\n",sizeof(struct _Person));return 0;
}

  通过sizeof函数计算该结构体的大小居然为16!没错,你没有看错!不是13,而是16。

  那么,问题来了,为什么是16呢?原来,对于int、short等放到结构体中保存是占用对应的字节,但是对于char*等,则只是保存它的指针(地址)。所谓地址,就是一个数字,那么这里就是一个整形数字代表内存地址,因此,它占4个字节,4+4+8=16。

  那么,问题又来了,假如我在main函数中,给name赋值了一个很长很长的字符串呢?

struct _Person p2 = { 0 };
p2.name = "刘德华刘德华刘德华刘德华刘德华刘德华刘德华刘德华刘德华刘德华";

  我们再次通过sizeof计算大小,仍然是16!为什么呢,我们可以通过下面这张图来看看:

  可以看到,无论我们为name赋值多么长的字符串,存储的永远只是一个指向具体字符串的指针,也就是一个地址(一个神奇的数字),结构体的大小不会因为具体指向的字符串的大小而变化。

2.3 使用typedef为结构体取别名

  前面的代码中,我们每次使用结构体的时候都要声明struct _Person ,比如:

struct _Person p1={0};
sizeof(struct _Person ); 

  这样显得比较麻烦,可以借助typedef来取一个别名:

typedef struct _Person
{char *name; // 指针为4个字节,地址(int)int age; // 4个字节double height; // 8个字节
} Person;int main(int argc, char *argv[])
{Person p = { 0 };p.name = "陈冠希";p.age = 34;printf("Name : %s , Age : %d\n",p.name,p.age);return 0;
}

  看看,是不是清爽得多?

三、结构体的拷贝赋值问题

3.1 结构体的复制其实是“深拷贝”

  在C语言中,结构体的复制其实就是将整体拷贝一份而不是将地址拷贝一份,这与在.NET中的深拷贝的概念是类似的(深拷贝和浅拷贝是.NET中一个比较重要的概念)。例如下面一段代码:

    Person p1 = { 0 };p1.name = "陈冠希";p1.age = 34;// 下面的复制其实是拷贝了一份Person p2 = p1;p1.age = 100;printf("p1.Name : %s , p1.Age : %d\n",p1.name,p1.age);printf("p2.Name : %s , p2.Age : %d\n",p2.name,p2.age);printf("Address : %d , %d\n",&p1,&p2);

  从下面的运行结果可以看出,即使我们在拷贝后改变了原p1的age,但p2的age仍为修改之前的值。最后,从两个结构体的内存地址可以看出,两个结构体是相互独立的内存空间(两块地址相隔了16个字节,刚好是该结构体的大小)。

3.2 如何实现结构体的“浅拷贝”

  假如我们要在一个程序中多次引用某个结构体,而不是希望每次复制都拷贝一份新的,这样会增加内存使用量,也就是我们在.NET中时常提到的浅拷贝(拷贝的只是引用地址)。于是,这时我们就可以使用一个指向结构体的指针来实现。

    Person* p3 = &p1;p1.age = 250;printf("p1.Name : %s , p1.Age : %d\n",p1.name,p1.age);printf("p3.Name : %s , p3.Age : %d\n",p3->name,p3->age);     // 对于结构体指针,取成员要使用->而不是.printf("Address : %d , %d\n",&p1,p3);

  这里需要注意的就是,对于结构体指针,取成员要使用 -> 而不是 .

  再来看看运行结果,发现两个地址一样,说明都是使用的同一块内存地址:

参考资料

  如鹏网,《C语言也能干大事(第三版)》

作者:周旭龙

出处:http://edisonchou.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

转载于:https://www.cnblogs.com/edisonchou/p/4663104.html

你必须知道的指针基础-6.内存的初始化及结构体的使用相关推荐

  1. 【C 语言】结构体 ( 结构体中嵌套一级指针 | 分配内存时先 为结构体分配内存 然后再为指针分配内存 | 释放内存时先释放 指针成员内存 然后再释放结构头内存 )

    文章目录 一.结构体中嵌套一级指针 1.声明 结构体类型 2.为 结构体 变量分配内存 ( 分配内存时先 为结构体分配内存 然后再为指针分配内存 ) 3.释放结构体内存 ( 释放内存时先释放 指针成员 ...

  2. 【Linux 内核 内存管理】虚拟地址空间布局架构 ③ ( 内存描述符 mm_struct 结构体成员分析 | mmap | mm_rb | task_size | pgd | mm_users )

    文章目录 一.mm_struct 结构体成员分析 1.mmap 成员 2.mm_rb 成员 3.get_unmapped_area 函数指针 4.task_size 成员 5.pgd 成员 6.mm_ ...

  3. C代码开发遇到的问题 变量初始化和结构体指针移动

    1. 变量初始化 函数内部的变量如果不初始化的话默认不是0而是一个随机值. 下面的程序用来打印一个未初始化的无符号的整型值,执行几遍,每次的结果都会不一样 #include <stdio.h&g ...

  4. c语言结构体学习整理(结构体初始化,结构体指针)

    渣渣c的c语言学习之路 1.关于c语言的结构体: 首先我们为什么要用到结构体,我们都已经学了很多int char -等类型还学到了同类型元素构成的数组,以及取上述类型的指针,在一些小应用可以灵活使用, ...

  5. 【Linux 内核 内存管理】虚拟地址空间布局架构 ② ( 用户虚拟地址空间组成 | 内存描述符 mm_struct 结构体源码 )

    文章目录 一.用户虚拟地址空间组成 二.内存描述符 mm_struct 结构体源码 一.用户虚拟地址空间组成 " 用户虚拟地址空间 " 包括以下区域 : ① 代码段 ② 数据段 ③ ...

  6. 【Linux 内核 内存管理】Linux 内核堆内存管理 ① ( 堆内存管理 | 内存描述符 mm_struct 结构体 | mm_struct 结构体中的 start_brk、brk 成员 )

    文章目录 一.堆内存管理 二.内存描述符 mm_struct 结构体 三.mm_struct 结构体中的 start_brk.brk 成员 一.堆内存管理 Linux 操作系统中的 " 堆内 ...

  7. Hive基础08、Hive引入Struct结构体

    Hive基础08.Hive引入Struct结构体 1.建表语句 创建一个温良贤淑女子的表 英文关键字解析: 1.CREATE TABLE 创建一个指定名字的表,如果库中已有相同名的表,则抛出异常: 用 ...

  8. c语言中较常见的由内存分配引起的错误_内存越界_内存未初始化_内存太小_结构体隐含指针...

    1.指针没有指向一块合法的内存 定义了指针变量,但是没有为指针分配内存,即指针没有指向一块合法的内浅显的例子就不举了,这里举几个比较隐蔽的例子. 1.1结构体成员指针未初始化 1 2 3 4 5 6 ...

  9. c语言中较常见的由内存分配引起的错误_内存越界_内存未初始化_内存太小_结构体隐含指针

    本篇是基于云天之巅博主音视频开发中的一个bug,继而查阅了的一点资料:本篇转载自博客园某博主的随笔,并做极少量的修改,原文地址:https://www.cnblogs.com/haore147/p/3 ...

  10. 专科 java转go 翱翔之路(一)基础语法:变量声明,匿名函数,结构体,函数,map

    本人专科!在太原干了一年了,想从java转go,上班中自学go,明年找go语言相关的工作 立帖为证! 待羽翼丰满,大风到来,便是我翱翔之时!!! 1.Go语言 1.命令 1.1 查看版本号 go ve ...

最新文章

  1. 您的用户配置文件没有正确加载
  2. 如何去掉域名中的www?
  3. fhq_treap || BZOJ 3223: Tyvj 1729 文艺平衡树 || Luogu P3391 【模板】文艺平衡树(Splay)...
  4. spring 注解说明以及@Resource和@Autowired的区别
  5. 计算机二级测试试题及答案,2017计算机二级测试题及答案解释
  6. wxpython图形编程_wxpython  图像编程
  7. linux mpeg-4,嵌入式MPEG-4解码系统的设计与实现,嵌入式MPEG-4解码系统,嵌入式Linux,视频码流,P...
  8. mysql启多_MySQL启多个实例
  9. 明机器人孔尧是哪里人_明机器人孔尧:希望未来打造场景机器人交流平台
  10. MySQL 时间戳转换成秒
  11. Windows NT Backup - 恢复工具 - Windows Vista的迁移工具
  12. Anylogic中队列属性设置
  13. MultipartFile 上传文件工具类
  14. 006输出9行内容,第1行输出1,第2行输出12,第3行输出123,以此类推,第9行输出123456789。
  15. 空闲时间不要接私活,要提升自己
  16. 2021计算机一级新增知识点,2021年全国计算机等级考试改革有哪些内容
  17. 【Python】关于安装爬虫框架scrapy的感悟
  18. Windows安装Maven(图文解说详细版)
  19. 洛阳计算机学校排名2015,洛阳初中名校排行榜TOP10,这一次你说了算!
  20. windos10本地安装git工具并使用

热门文章

  1. Android DataBinding 详解
  2. 【CVPR 2022】目标检测SOTA:DINO: DETR with Improved DeNoising Anchor Boxes for End-to-End Object Detection
  3. 2023年中央民族大学新闻学保研必看上岸前辈复习经验分享
  4. [读书笔记]组件设计:补白[深入剖析ASP.NET组件设计]一书中HttpApplication对象创建的细节...
  5. ubuntu硬盘序列号怎么查询_linux查看设备和硬盘序列号 ip mac地址
  6. hdmi线推荐微型计算机,几款高清线对比下来,这款mini dp转HDMI最实用
  7. 含泪整理最优质鲜肉VRay材质球素材,你想要的这里都有
  8. hdu 5285 wyh2000 and pupil【二分图+染色法】
  9. 0602 信用卡防盗刷学习总结
  10. 黑客攻击_我如何开始黑客攻击