C语言学习之路(高级篇)—— 变量和内存分布(上)

news2024/11/17 9:29:37

说明:该篇博客是博主一字一码编写的,实属不易,请尊重原创,谢谢大家!

数据类型

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,可以简化结构体关键字struct
	myPerson p2 = { "李四", 20 };
}

简化以上写法

//主要用途  给类型起别名
//语法  typedef  原名  别名
typedef struct Person
{
	char name[64];
	int age;
}myPerson;

void test01()
{	
	// 未使用typedef,在初始化成员时需要加struct修饰
	struct Person p1 = { "张三", 19 };
	// 使用typedef,可以简化结构体关键字struct
	myPerson p2 = { "李四", 20 };
}

2.2 区分数据类型

typedef使用,区分数据类型

// 2、区分数据类型
void test02()
{
	char* p1, p2; // p1是char *  而 p2 是char
}

通过c++里面的typeid方法去验证p1p2的数据类型

#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来让p1p2都为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 操作符

sizeofc语言中的一个操作符,类似于++--等等。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 = 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
}

打印出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~3
	int b; // 4~7
	char c; // 8~11
	int d; // 12~15
};

那么struct MyStruct0~15范围,占16个字节

在这里插入图片描述

知道结构体的大小后,那么p+1就跨过整个结构体了,打印验证pp+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步长就可以达到开始地址为12d属性,所以指针类型的不同,跳跃的步长是不一样的,你想访问哪个位置的属性,可以自己来进行控制,前提是对内存地址的足够了解。

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));

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/73037.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

04 | 云硬盘的使用方法

前期环境&#xff1a; Ubuntu 0 云硬盘类型 云硬盘类型包括&#xff1a; 高性能云硬盘通用型 SSD 云硬盘SSD 云硬盘增强型 SSD 云硬盘极速型 SSD 云硬盘&#xff0c;仅支持随存储增强型云服务器一同购买&#xff0c;不支持单独购买 1 创建云硬盘 1.1 创建方式 1.1.1 单个…

第二证券|连拉20CM涨停!防疫新概念股火了!恒生科技指数涨逾5%

周四上午&#xff0c;“新十条”发布后&#xff0c;由于A股商场已反弹一段时刻&#xff0c;两市股指今天早盘接连震动走势&#xff0c;港股在地产、科技、消费等板块带动下&#xff0c;体现更为强势。 A股上证指数早盘在3200点附近持续震动&#xff0c;光伏、化肥、物流、港口等…

JavaScript内置对象(内置对象、查文档(MDN)、Math对象、日期对象、数组对象、字符串对象)

目录 JavaScript内置对象 内置对象 查文档 MDN Math对象 Math概述 案例一&#xff1a;封装自己的对象 随机数方法 random() 案例一&#xff1a;猜数字游戏 日期对象 Date 概述 Date()方法的使用 获取日期的总的毫秒形式 案例一&#xff1a;倒计时效果 数组对象 …

DoltLab本地部署实践

目录引言Dolt是什么&#xff1f;如何本地部署使用DoltLab具体安装步骤安装期间FAQ写在最后其他相关资料引言 自从搞深度学习训练模型以来&#xff0c;一直有个问题困扰着我&#xff1a;训练所用数据集的管理。为什么说这是一个问题呢&#xff1f; 在读研时&#xff0c;我们依据…

ELK日志分析系统概述及部署

文章目录一、ELK日志分析系统1、概念2、完整日志系统基本特征3、使用ELK的原因4、ELK 的工作原理二、ELK日志分析系统集群部署的操作步骤环境准备&#xff1a;1、 ELK Elasticsearch 集群部署&#xff08;在Node1、Node2节点上操作&#xff09;1.1、更改主机名、配置域名解析、…

剑指 Offer 53 - I. 在排序数组中查找数字 I

摘要 剑指 Offer 53 - I. 在排序数组中查找数字 I 一、二分查找 1.1 二分查找的分析 由于数组已经排序&#xff0c;因此整个数组是单调递增的&#xff0c;我们可以利用二分法来加速查找的过程。 考虑 target在数组中出现的次数&#xff0c;其实我们要找的就是数组中「第一…

汇编语言ch2_2 汇编语言中的debug

使用debug 可以完成以下功能&#xff1a; 可以查看 和改变 CPU 中&#xff0c;寄存器的内容&#xff1b;可以查看 和改变内存中的内容&#xff1b;可以将内存中的 机器指令 翻译成汇编指令使用汇编指令 在 内存中 存入 机器指令执行机器指令 首先&#xff0c;启动 Debug,在DO…

实现数智内控,数据分析创造价值——辽宁烟草智能风险体检系统

近两年&#xff0c;烟草行业部分单位围绕中心任务&#xff0c;结合实际&#xff0c;守正创新&#xff0c;开展了许多研究探索。比如&#xff0c;在财务大数据价值挖掘、会计共享中心建设、财务风险预警系统建设等方面做了大量卓有成效的工作。在这样的背景下&#xff0c;辽宁烟…

DSPE-MAL 磷脂改性马来酰亚胺简介CAS1360858-99-6

DSPE-MAL二硬脂酰磷脂酰乙醇胺改性马来酰亚胺 中文名称&#xff1a;二硬脂酰磷脂酰乙醇胺改性马来酰亚胺 英文名称&#xff1a;DSPE-MAL CAS&#xff1a;1235864-97-7 分子式&#xff1a;C48H86N2NaO11P 分子量&#xff1a;921.16700 外观&#xff1a;白色粉末 DSPE-MAL二…

2022icpc 济南站 持续补题

链接&#xff1a;Dashboard - 2022 International Collegiate Programming Contest, Jinan Site - Codeforces 签到题&#xff1a;k K. Stack Sort You are given a permutation with nn numbers, a1,a2,…,an(1≤ai≤n,ai≠aj when i≠j). You want to sort these numbers …

WY易盾cb、fp逆向分析

内容仅供参考学习 欢迎朋友们V一起交流&#xff1a; zcxl7_7 目标 网址&#xff1a;案例地址 这个好像还没改版&#xff0c;我看官网体验那边已经进行了混淆 分析 这个进行的请求很乱&#xff0c;我就不说怎么找的了&#xff0c;到时候越听越乱。一共有2个请求很重要 …

笔试题之编写SQL按要求查询用户阅读行为数据

紧张源于恐惧&#xff0c;恐惧源于未知。 文章目录前言一、SQL题目二、当时作答结果三、复盘&#xff08;一&#xff09;建表并自定义插入数据&#xff08;二&#xff09;正确解答&#xff08;三&#xff09;答错原因分析总结前言 分享本人一次失败的笔试经历&#xff0c;供各…

plink中的BGEN格式的数据如何用

这里&#xff0c;介绍一下BGEN格式的数据&#xff0c;他的文件格式是这样的&#xff1a;a.bgen&#xff0c;这是一个新的数据格式&#xff0c;目前应用不如plink的二进制文件&#xff1a;.bim,.bed,.fam。这里介绍一下如何相互转换。 1. bgen格式介绍 现代遗传关联研究通常使…

[附源码]计算机毕业设计JAVA中小企业人事管理系统

[附源码]计算机毕业设计JAVA中小企业人事管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM my…

HMS Core 6.8.0版本发布公告

分析服务 ◆ 游戏行业新增“区服分析”埋点模板及分析报告&#xff0c;支持开发者分服务器查看用户付费、留存等指标&#xff0c;可进一步评估不同服务器的玩家质量&#xff1b; ◆ 新增营销活动报告&#xff0c;可查看广告任务带来的曝光、点击相关信息&#xff0c;让营销推…

阿里P7晒工资条,看完真的扎心了……

前几天&#xff0c;有位老粉私信我&#xff0c;说看到某95后学弟晒出阿里P7的工资单&#xff0c;他是真酸了…想狠补下技术&#xff0c;努力冲一把大厂。 为了帮到他&#xff0c;也为了大家能在最短的时间内做面试复习&#xff0c;我把软件测试面试系列都汇总在这一篇文章了。…

关于信息系统监理师考试怎么备考?

信息系统监理师是属于软考的中级科目&#xff0c;是水平考试&#xff0c;取得证书后就具备了任职中级职称的资格&#xff0c;并可以注册为信息系统监理师&#xff0c;进行信息系统监理工程师的执业工作。 注册监理工程师&#xff0c;是指经考试取得中华人民共和国监理工程师资…

041-推箱子游戏1

上一讲:040-JAVA集合及GUI综合应用(实现简单的订单管理系统)_CSDN专家-赖老师(软件之家)的博客-CSDN博客 下一讲:推箱子游戏源代码 摘要: 1、使用JAVA基础知识 2、GUI界面编程实现推箱子界面,常用控件的综合应用; 3、使用JAVA绘图技术实现推箱子过程的绘图功能;…

追觅身陷「多事之秋」!一场无法投机的「卡位战」

清洁电器市场正释放着新的商业活力。 GfK数据显示&#xff0c;今年上半年在整体家电市场低迷的同时&#xff0c;清洁机器人市场零售额保持了同比15%的增速&#xff0c;仍然是家电行业的主要增长点。其中&#xff0c;线上市场扫地机器人销售仍然居首位&#xff0c;但洗地机反超…

项目笔记:Arduino读取SD卡

1 硬件连接&#xff08;使用Arduino Uno&#xff09;&#xff1a; CS -> 10 SCK -> 13 MOSI -> 11 MISO -> 12 VCC ->5V GND -> GND 2 让Arduino检测到SD卡 官方测试程序&#xff1a;检测SD卡连接并输出卡型号 /*SD card testThis example shows how use…