C语言学习之路(高级篇)—— 变量和内存分布(上)
说明:该篇博客是博主一字一码编写的,实属不易,请尊重原创,谢谢大家!
数据类型
1) 数据类型概念
什么是数据类型?为什么需要数据类型?
数据类型是为了更好进行内存的管理,让编译器能确定分配多少内存。
我们现实生活中,狗是狗,鸟是鸟等等,每一种事物都有自己的类型,那么程序中使用数据类型也是来源于生活。
当我们给狗分配内存的时候,也就相当于给狗建造狗窝,给鸟分配内存的时候,也就是给鸟建造一个鸟窝,我们可以给他们各自建造一个别墅,但是会造成内存的浪费,不能很好的利用内存空间。
我们在想,如果给鸟分配内存,只需要鸟窝大小的空间就够了,如果给狗分配内存,那么也只需要狗窝大小的内存,而不是给鸟和狗都分配一座别墅,造成内存的浪费。
当我们定义一个变量,a = 10
,编译器如何分配内存?计算机只是一个机器,它怎么知道用多少内存可以放得下10
?
所以说,数据类型非常重要,它可以告诉编译器分配多少内存可以放得下我们的数据。
狗窝里面是狗,鸟窝里面是鸟,如果没有数据类型,你怎么知道冰箱里放得是一头大象!
数据类型基本概念:
- 类型是对数据的抽象;
- 类型相同的数据具有相同的表示形式、存储格式以及相关操作;
- 程序中所有的数据都必定属于某种数据类型;
- 数据类型可以理解为创建变量的模具: 固定大小内存的别名;
2) 数据类型别名
示例代码:
#define _CRT_SECURE_NO_WARNINGS // VS不建议使用传统库函数,如果不用这个宏,会出现一个错,编号:C4996
#include <stdio.h> // std 标准 i input 输入 o output 输出
#include <stdlib.h> // strcpy strcmp strcat strstr
#include <string.h> // // malloc free//程序入口
int main()
{system("pause"); // 按任意键暂停 阻塞功能return EXIT_SUCCESS; //返回 正常退出值 0
}
2.1 简化结构体关键字
typedef
使用,简化结构体关键字 struct
struct Person
{char name[64];int age;
};
typedef struct Person myPerson;void test01()
{ // 未使用typedef,在初始化成员时需要加struct修饰struct Person p1 = { "张三", 19 };// 使用typedef,可以简化结构体关键字structmyPerson p2 = { "李四", 20 };
}
简化以上写法
//主要用途 给类型起别名
//语法 typedef 原名 别名
typedef struct Person
{char name[64];int age;
}myPerson;void test01()
{ // 未使用typedef,在初始化成员时需要加struct修饰struct Person p1 = { "张三", 19 };// 使用typedef,可以简化结构体关键字structmyPerson p2 = { "李四", 20 };
}
2.2 区分数据类型
typedef
使用,区分数据类型
// 2、区分数据类型
void test02()
{char* p1, p2; // p1是char * 而 p2 是char
}
通过c++
里面的typeid
方法去验证p1
、p2
的数据类型
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;int main()
{char* p1, p2;printf("p1的数据类型为:%s\n", typeid(p1).name());printf("p2的数据类型为:%s\n", typeid(p2).name());system("pause");return EXIT_SUCCESS;
}
通过typedef
来让p1
、p2
都为char *
数据类型
// 2、区分数据类型
void test02()
{//char* p1, p2; // p1是char * 而 p2 是char// 通过typedef来区分数据类型typedef char* PCHAR;PCHAR p1, p2;char *p3, *p4; // p3 和 p4都是char *
}
2.3 提高代码移植性
比如将以下代码放在C89下面去运行,C89不支持long long 类型即无法运行程序,那么就需要将所有的long long类型进行更改为int整型,这样很机械
void test03()
{long a = 10;long b = 20;
}
所以为了避免这种类似情况的发生(目前编译器支持都在C99以上标准,只是举例说明),使用typedef
后,那么只需要去替换typedef long long MYINT;
中的 long
long
就可以了
//3、提高代码移植性
typedef int MYINT; //typedef long long MYINT; 只需要替换 long long 就可以了
void test03()
{MYINT a = 10;MYINT b = 10;
}
3) void 数据类型
void
字面意思是 “ 无类型 ” ,void*
无类型指针(万能指针),无类型指针可以指向任何类型的数据。
void
定义变量是没有任何意义的,当你定义void a
,编译器会报错。
void
真正用在以下两个方面:
- 对函数返回的限定;
- 对函数参数的限定;
3.1 无类型是不可以创建变量的
// 1、无类型是不可以创建变量的
void test04()
{void a = 10; // error 编译器直接报错,因为不知道给a分配多少内存空间
}
3.2 可以限定函数返回值
void func01()
{return 10;
}void test05()
{printf("%d\n", func01()); // error %d 是需要整型格式,但是func01方法具有viod类型,所以出错
}
//2、可以限定函数返回值
void func01()
{return 10;
}void test05()
{ func01(); // 即使可以编译过去,但是会给出一个警告//printf("%d\n", func()); // error %d 是需要整型格式,但是func方法具有viod类型,所以出错
}
3.3 可以限定函数参数列表
调用无参函数时,传递参数
int func02()
{return 10;
}
void test06()
{ func02(10, 20); // 编译成功,无报错和警告,这也是c语言中存在不严谨的地方之一
}
函数参数为void
//3、限定函数参数列表
int func02(void)
{return 10;
}
void test06()
{ func02(10, 20); // 编译成功,无错误,但是有警告,会提示我们这里其实存在问题
}
3.4 可以作为万能指针类型
打印void *
万能指针的大小
//4、void * 万能指针
void test07()
{void * p = NULL;printf("size of void * = %d\n", sizeof(p)); // 4个字节
}
不同数据类型的指针之间赋值
//4、void * 万能指针
void test07()
{void* p = NULL;printf("size of void * = %d\n", sizeof(p)); // 4个字节int* pInt = NULL;char* pChar = NULL;pInt = pChar; // 编译无误,会警告 “char *”到“int *”的类型不兼容
}
不同数据类型的指针之间赋值,需要进行强制转换才不会出警告
//4、void * 万能指针
void test07()
{void* p = NULL;printf("size of void * = %d\n", sizeof(p)); // 4个字节int* pInt = NULL;char* pChar = NULL;//pInt = pChar; // 警告 “char *”到“int *”的类型不兼容pInt = (int *)pChar;
}
万能指针可以不通过强制类型转换,就可以转为任意类型的指针
//4、void * 万能指针
void test07()
{void* p = NULL;printf("size of void * = %d\n", sizeof(p)); // 4个字节int* pInt = NULL;char* pChar = NULL;//pInt = pChar; // 警告 “char *”到“int *”的类型不兼容pInt = (int *)pChar; // 不同数据类型的指针之间赋值,需要进行强制转换才不会出警告pInt = p; // 万能指针可以不通过强制类型转换,就可以转为任意类型的指针
}
4) sizeof 操作符
sizeof
是c
语言中的一个操作符,类似于++
、--
等等。sizeof
能够告诉我们编译器为某一特定数据或者某一个类型的数据在内存中分配空间时分配的大小,大小以字节为单位。
基本语法:
sizeof(变量); sizeof 变量; sizeof(类型);
sizeof 注意点:
sizeof
返回的占用空间大小是为这个变量开辟的大小,而不只是它用到的空间。和现今住房的建筑面积和实用面积的概念差不多。所以对结构体用的时候,大多情况下就得考虑字节对齐的问题了;sizeof
返回的数据结果类型是unsigned int
;- 要注意数组名和指针变量的区别。通常情况下,我们总觉得数组名和指针变量差不多,但是在用
sizeof
的时候差别很大,对数组名用sizeof
返回的是整个数组的大小,而对指针变量进行操作的时候返回的则是指针变量本身所占得空间,在32
位机的条件下一般都是4
。而且当数组名作为函数参数时,在函数内部,形参也就是个指针,所以不再返回数组的大小;
4.1 sizeof 基本用法
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>// sizeof是不是一个函数?
// 1、本质不是一个函数, 是一个运算符,如 + - * /
void test08()
{// sizeof在统计类型的时候,是需要添加小括号的printf("sizeof int = %d\n", sizeof(int));// sizeof在统计变量的时候,是可以不加小括号的double d = 3.14;printf("sizeof d = %d\n", sizeof d);
}int main()
{ test08();system("pause");return EXIT_SUCCESS;
}
输出结果
sizeof int = 4
sizeof d = 8
4.2 sizeof 结果类型
首先在验证sizeof
返回结果是unsigned int
类型之前,我们先看一下无符号和有符号类型的运算结果。
// 2、sizeof返回值是什么? unsigned int
void test09()
{unsigned int a = 10; // 如果一个unsigned int 和 int进行运算,那么会将结果统一转换为 unsigned int 类型if (a - 20 >0){printf("结果大于0\n");}else{printf("结果小于0\n");}
}int main()
{ //test08();test09();system("pause");return EXIT_SUCCESS;
}
运算结果
结果大于0
通过以上判断结果,我们就可以去验证sizeof
的返回类型是否是unsigned int
类型了
void test09()
{if (sizeof(int)-5 > 0){printf("结果大于0\n");printf("%u\n", sizeof(int) - 5);}
}
4.3 sizeof 其他用法
统计数组占用内存空间大小
// 3、sizeof其他用法
// 统计数组占用内存空间大小
void test10()
{int arr[] = { 1,2,3,4,5,6,7,8,9,10 };printf("sizeof(arr) = %d\n", sizeof(arr)); // 40 int类型4*10个元素;
}
说明一点,数组名传入函数作为形参后,会退化为一个指针,指针指向数组第一个元素的地址
void calcArray(int arr[]) // 当数组名传入到函数中,会被退化成一个指针,指向数组中第一个元素的地址 int arr[] 就等价于 int *arr
{printf("calcArray->sizeof(arr) = %d\n", sizeof(arr)); // 4}
// 3、sizeof其他用法
// 统计数组占用内存空间大小
void test10()
{int arr[] = { 1,2,3,4,5,6,7,8,9,10 };printf("sizeof(arr) = %d\n", sizeof(arr)); // 40 int类型4*10个元素;calcArray(arr);
}
5) 数据类型总结
- 数据类型本质是固定内存大小的别名,是个模具,
C
语言规定:通过数据类型定义变量; - 数据类型大小计算(
sizeof
); - 可以给已存在的数据类型起别名
typedef
; - 数据类型的封装(
void
万能类型);
变量
1) 变量的概念
既能读又能写的内存对象,称为变量;
若一旦初始化后不能修改的对象则称为常量。
变量定义形式: 类型 标识符, 标识符, … , 标识符
2) 变量名的本质
- 变量名的本质:一段连续内存空间的别名;
- 程序通过变量来申请和命名内存空间
int a = 0
; - 通过变量名访问内存空间;
- 不是向变量名读写数据,而是向变量所代表的内存空间中读写数据;
3) 修改变量的两种方式
3.1 直接修改
void test11()
{// 1、直接修改int a = 10;a = 20;printf("a = %d\n", a); // 20
}int main()
{test11();system("pause");return EXIT_SUCCESS;
}
3.2 间接修改
void test11()
{// 1、直接修改int a = 10;a = 20;printf("a = %d\n", a); // 20// 2、间接修改int* p = &a;// *p 表示解引用*p = 30;printf("a = %d\n", a); // 30
}
3.3 自定义数据类的修改
struct MyStruct
{char a;int b;char c;int d;
};void test12()
{struct MyStruct m1 = { "a", 10, "b", 20 };// 直接修改d属性m1.d = 200;printf("m1.d = %d\n", m1.d); // m1.d = 200// 间接修改d属性struct MyStruct* p = &m1;p->d = 2000;printf("p->d = %d\n", p->d); // p->d = 2000
}
以上间接修改d
属性的最简单的方式,我们还可以通过步长来找到d
属性在内存中的位置;首先先来看看struct MyStruct
结果体的大小
void test12()
{struct MyStruct m1 = { "a", 10, "b", 20 };// 直接修改d属性的值m1.d = 200;printf("m1.d = %d\n", m1.d); // m1.d = 200struct MyStruct* p = &m1;p->d = 2000;printf("p->d = %d\n", p->d); // p->d = 2000printf("%d\n", sizeof(struct MyStruct)); // 16
}
打印出struct MyStruct
结果体的大小为16
字节,16
字节是如何计算的,char a;
从首地址0
开始到哪里看下一个int b;int b
从几开始必须为int
类型的整数倍也就是4
,那么char a;
就占0~3
,直接全部都给了char a;
因为遵循内存对齐的方式,所以后面的3个都没有用;int b;
占4
个字节,所以为4~7
,依次类推。
struct MyStruct
{char a; // 0~3int b; // 4~7char c; // 8~11int d; // 12~15
};
那么struct MyStruct
从0~15
范围,占16
个字节
知道结构体的大小后,那么p+1
就跨过整个结构体了,打印验证p
和p+1
地址差值
printf("p = %d\n", p);
printf("p+1 = %d\n", p+1);
struct MyStruct
类型的指针显然无法通过步长来指向d
属性,那么可以定义char
类型指针,一个一个的跳,因为我们目前知道d
属性位置是从12
开始的,那么char类型指针变量p+12
就行了,但是要注意:因为p+12为char*类型,而d属性为int类型,如果不强转只能取一个字节的数据,所以需要强转后再解引用*
void test12()
{struct MyStruct m1 = { "a", 10, "b", 20 };// 直接修改d属性的值m1.d = 200;printf("m1.d = %d\n", m1.d); // m1.d = 200//struct MyStruct* p = &m1;//p->d = 2000;//printf("p->d = %d\n", p->d); // p->d = 2000//printf("%d\n", sizeof(struct MyStruct)); // 16//printf("p = %d\n", p);//printf("p+1 = %d\n", p+1);char* p = &m1;printf("*(int *)(p + 12) = %d\n", *(int *)(p + 12)); // 因为p+12为char*类型,而d属性为int类型,如果不强转只能取一个字节的数据,所以需要强转后再解引用*
}
同理,如果是先将char
类型指针p
强转为int *
类型,那么p+3
步长就可以达到开始地址为12
的d
属性,所以指针类型的不同,跳跃的步长是不一样的,你想访问哪个位置的属性,可以自己来进行控制,前提是对内存地址的足够了解。
char* p = &m1;
printf("*(int *)(p + 12) = %d\n", *(int *)(p + 12)); // 因为p+12为char*类型,而d属性为int类型,如果不强转只能取一个字节的数据,所以需要强转后再解引用*
printf("*((int *)p+3) = %d\n", *((int *)p+3));
C语言学习之路(高级篇)—— 变量和内存分布(上)相关推荐
- 2017c语言自学贴吧,【图片】17年1.9开启我的C语言学习之路—小白篇【c语言吧】_百度贴吧...
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 什么是程序呐? 我们说这个程序与你的生活有关,通常是达到某个目标或解决某个问题中所需要的步骤或过程,这一系列的步骤就是程序. 生活中的程序与计算机中的程序 ...
- 12天学好C语言——记录我的C语言学习之路(Day 12)
12天学好C语言--记录我的C语言学习之路 Day 12: 进入最后一天的学习,用这样一个程序来综合考量指针和字符串的关系,写完这个程序,你对字符串和指针的理解应该就不错了. //输入一个字符串,内有 ...
- Go语言学习之路(二)
Go语言学习之路(二) 面对对象编程思想 抽象 封装 继承 接口 文件 命令行参数 Json 序列化 反序列化(unmarshal) 单元测试 Redis Redis简介 Redis基本使用 Go连接 ...
- 大数据学习之路 JUC篇
大数据学习之路 JUC篇(1) 前提说明 本人是一名学生,茫茫it行业的一名卑微的小白,这是我第一次写博客.其原因是学着学着知识发现回顾的时候差不多全忘记了!!为了总结.复习自己以往学到过的有关大数据 ...
- JAVA学习之路--基础篇三
目录 关于Java中从键盘输入的语句 nextxxx().next().nextLine()的区别 语句 if和if else语句 Switch语句 for语句 while和do..while bre ...
- C语言-学习之路-01
C语言学习之路-01 目录 关键字 数据类型 常量 变量 声明和定义 进制 sizeof关键字 整型:int short.int.long.long long 字符型:char ASCII对照表 转义 ...
- RabbitMQ学习笔记(高级篇)
RabbitMQ学习笔记(高级篇) 文章目录 RabbitMQ学习笔记(高级篇) RabbitMQ的高级特性 消息的可靠投递 生产者确认 -- confirm确认模式 生产者确认 -- return确 ...
- C语言学习历程--小项目篇(1)
C语言学习历程–小项目篇–基于winpcap的UDP数据发送 开发环境介绍 1.操作系统:windows10(基于x64处理器).IDE:vs 2019(微软官网个人免费版).winpcap安装包及开 ...
- go每日新闻(2021-05-21)——细谈Go变量的内存分布
每日一谚:"Make it correct, make it clear, make it concise, make it fast. In that order." - Wes ...
最新文章
- html 逗号用什么替换,字符串用javascript数组中的逗号替换“↵”
- 使用Scrapy构建一个网络爬虫
- 自律到极致-人生才精致:第12期
- ASP.NET MVC 视图
- java中qq中拉伸的文件,delphi中如何实现QQ中的截图并实现拉伸放大移动的功能
- 服务器即将维护完成,新一轮大服务器即将开启:3月26日维护8小时
- “新闻”频道“最新更新”有问题吗?
- windows下发文件到linux中文名乱码解决办法
- WinSock IO模型五: 完成端口
- python 断言大全
- 论文的字数是怎么算的
- 专业技能与职业素养报告计算机,学生专业技能与职业素养专题报告怎么写
- 无限滚动新一代老虎机
- 服务器防火墙firewalld,指定端口开放
- 开心网辅助程序--开心网争车位助手正式发布(含源码)
- CRAFT:Character region awareness for text detection 论文阅读
- 一文带你了解知识图谱融入预训练模型哪家强?九大模型集中放送
- CSS ::backdrop
- php敏感字符串过滤_PHP实现敏感词过滤
- 观察 | 家长焦虑,教培着急,暑期“培训热”今年还会持续吗?
热门文章
- 优思学院:初学者应如何自学ISO9001质量管理体系?
- java web 拍卖系统_java SSH网上拍卖平台系统
- 关于sudo dpkg-divert –local –rename –add /sbin/initctl导致的开机无图标解决方法
- 速度控制与转矩控制的区别
- Unity 小游戏:3D射箭
- phpcms 更换新域名更新栏目url和内容页url无法更新解决方法
- 群晖 Let's Encrypt 泛域名证书自动更新
- 就追求效率的趋势谈谈星际的发展
- CNN终于杀回来了!京东AI开源最强ResNet变体CoTNet:即插即用的视觉识别模块
- Django配置Bootstrap, js