learn_C_deep_10 extern在多文件下的理解、struct 关键字的理解与柔性数组、union 的内存级布局理解、enum 关键字的基本理解、typedef 的理解与分类、关键字总结

简介: learn_C_deep_10 extern在多文件下的理解、struct 关键字的理解与柔性数组、union 的内存级布局理解、enum 关键字的基本理解、typedef 的理解与分类、关键字总结

extern 在多文件下的理解与使用


       extern 是一个关键字,用于在程序中声明外部变量或函数。

变量的声明通常包含变量的类型和名称,但是如果变量定义在一个源文件中,而其它源文件也需要访问这个变量,则需要在其它源文件中声明该变量。此时可以使用 extern 关键字来告诉编译器,该变量已经在其它文件中定义好了,不需要再重新分配存储空间,只需要使用它即可。


例如:

在编译 file2.c 文件时,由于该文件中使用了 extern 关键字声明了变量 x,所以编译器不会分配新的存储空间,而是使用 file1.c 中已经定义好的变量 x。 函数的情况类似,如果一个函数定义在一个源文件中,而其它源文件也需要调用该函数,则需要使用 extern 关键字来声明该函数。


例如:

在编译 main.c 文件时,由于该文件中包含了 func.h 头文件,其中使用了 extern 关键字声明了函数 func,所以编译器知道该函数已经在其它源文件中定义好了,可以直接在链接时链接函数即可。


struct 关键字的理解与柔性数组


       在C语言中,struct是一个关键字,用于定义结构体。结构体是由多个不同类型的数据组成的集合,可以将它们组合成一个自定义的数据类型。结构体可以包含不同的数据类型,如整型、浮点型、字符型、数组以及其他结构体等,它们可以组合成复杂的数据结构。


例如学生:

 这段代码定义了一个包含姓名、年龄、性别和体重的学生结构体Stu,并在main函数中定义了一个Stu类型的变量s,并为其赋值。接着定义了一个指向Stu结构体的指针p,并将其指向变量s的内存地址。最后,通过指针p访问结构体中的成员变量,并将它们打印出来,分别使用了结构体指针的两种访问方式:(*p).name 和 p->name,它们的作用是一样的。


空结构体的大小



这段代码中定义了一个名为S的结构体,但是结构体里面没有任何成员。在代码中调用了sizeof操作符,对结构体S进行计算其大小的操作,但vs编译器在编译这段代码时会报错。根据C语言的规定,定义一个结构体或者联合体时,至少需要包含一个成员,否则这个结构体是无效的。


  这段代码中定义了一个名为S的结构体,但是结构体里面没有任何成员。在代码中调用了sizeof操作符,对结构体S进行计算其大小的操作,但gcc编译器在编译这段代码时不会报错,求得大小为0,并且可以定义变量(可以定义空间大小为0的变量),但是没有空间,不能进行初始化。


柔性数组


#include<stdio.h>
#include<stdlib.h>
struct S
{
  int num;
  int arr[];
};
int main()
{
  printf("%d\n", sizeof(struct S));//柔性数组不占用空间
  struct S* p = malloc(sizeof(struct S) + sizeof(int) * 10);
  int i = 0;
  p->num = 10;
  for (i = 0; i < p->num; i++)
  {
    p->arr[i] = i;
  }
  free(p);
  p = NULL;
  return 0;
}


union 的内存级布局理解


 在C语言中,union是一种特殊的数据类型,它允许您在同一内存位置存储不同的数据类型。它类似于结构体,但是它的所有成员共享同一内存空间,因此只能够保存其中一个成员的值。 定义一个union变量的语法与结构体相似,


例如:


这个union类型变量un可以保存整数int、字符char。但是,不能同时保存这两种类型,因为它们共享同一个内存空间。 Union的大小由其最大成员的大小确定,例如上面的例子中,un变量的大小是4字节,因为整形类型的成员是这个联合体中最大的。


基本认识与补充认识



union的内存布局



大小端对于union的影响



小练习

//联合体内部的成员起始地址都是一样的!!!
#include<stdio.h>
union un {
  int a;
  char c[4];
}*p,u;//全局变量*p、u
int main()
{
  p = &u;
  p->c[0] = 0x39;
  p->c[1] = 0x38;
  p->c[2] = 0x37;
  p->c[3] = 0x36;
  printf("0x%x\n", p->a);
  return 0;
}


enum 关键字的基本理解



enum 与 #define 的区别



typedef 的理解与分类



typedef 与 #define 的区别


1. 定义方式不同

       - typedef 是一种类型定义,用于给已有类型取一个新的名字,是在编译期间解析。

- #define 是一种预处理编译指令,用于替换代码中的文本宏,是在预处理阶段解析,即在编译器处理代码之前。


2. 定义的作用域不同

       - typedef 定义的作用域是局部的,只在当前定义的作用域内有效。

       - #define 定义的作用域是全局的,从定义位置开始到文件末尾都有效。


3. 可读性和维护性不同

       - typedef 的定义会产生一个新的类型名,使得代码更容易理解和维护。

       - #define 定义的宏不会改变原有的类型或者结构体,容易引起混淆和错误。


4. 可以对已有类型(包括自定义类型)进行重定义

       - 使用 typedef 语句,可以为 C 语言中已有的类型定义一个新的名字。

       - #define 指令无法直接对 C 语言中已有的类型进行重定义。


总的来说,typedef 适用于定义新的数据类型或别名,而 `#define` 适用于定义常量和函数宏等。

//问题1:
typedef int * ptr_t;
ptr_t p1,p2;
问:p1,p2分别是什么类型
//问题2:
#define PTR_T int*
PTR_T p1, p2;
问:p1,p2分别是什么类型


问题1:p1和p2分别是指向int类型的指针变量。由于typedef int * ptr_t; 定义了一个名为ptr_t的类型,它是指向int类型的指针类型,因此ptr_t p1,p2; 相当于 int* p1, p2; 即p1和p2都是指向int类型的指针变量。

问题2中,#define PTR_T int* 定义了一个名为PTR_T的宏,将int*替换为PTR_T。因此 PTR_T p1, p2; 相当于 int* p1, p2; 这里,p1是指向int类型的指针变量,而p2是int类型的普通变量。但是这种定义方式可能会带来理解上的混淆,因为它让人很难区分p1和p2的类型,不建议使用。正确的做法是使用typedef类型定义来明确变量的数据类型。

#include <stdio.h>
#define INT32 int
typedef int int32;
int main()
{
  //unsigned int32 a = 10; C中typedef不支持这种类型的扩展,不能当成简单的宏替换
  unsigned INT32 a = 20; //宏简单替换,可以
  return 0;
}

   在typedef中,INT32类型仅被视为一种新的数据类型,C中typedef不支持这种类型的扩展,不能当成简单的宏替换,而在宏定义中,INT32被视为简单的字符替换。


关键字总结


数据类型关键字(12个)



控制语句关键字(12个)



存储类型关键字(5个)



注意:存储关键字,不可以同时出现,也就是说,在一个变量定义的时候,只能有一个。


其他关键字(3个)



现在我们来看看这个有没有问题

typedef static int s_int;


error C2159: 指定了一个以上的存储类,因为static和typedef都是存储类型关键字,而存储类型关键字不可以同时出现,所以不能这样写代码。

相关文章
|
1月前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
53 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
1月前
|
存储 Java
深入理解java对象的内存布局
这篇文章深入探讨了Java对象在HotSpot虚拟机中的内存布局,包括对象头、实例数据和对齐填充三个部分,以及对象头中包含的运行时数据和类型指针等详细信息。
28 0
深入理解java对象的内存布局
|
1月前
|
Linux C++
Linux c/c++文件虚拟内存映射
这篇文章介绍了在Linux环境下,如何使用虚拟内存映射技术来提高文件读写的速度,并通过C/C++代码示例展示了文件映射的整个流程。
46 0
|
1月前
|
程序员 Windows
程序员必备文件搜索工具 Everything 带安装包!!! 比windows自带的文件搜索快几百倍!!! 超级好用的文件搜索工具,仅几兆,不占内存,打开即用
文章推荐了程序员必备的文件搜索工具Everything,并提供了安装包下载链接,强调其比Windows自带搜索快且占用内存少。
43 0
|
3月前
|
存储 算法 Oracle
不好意思!耽误你的十分钟,JVM内存布局还给你
先赞后看,南哥助你Java进阶一大半在2006年加州旧金山的JavaOne大会上,一个由顶级Java开发者组成的周年性研讨会,公司突然宣布将开放Java的源代码。于是,下一年顶级项目OpenJDK诞生。Java生态发展被打开了新的大门,Java 7的G1垃圾回收器、Java 8的Lambda表达式和流API…大家好,我是南哥。一个Java学习与进阶的领路人,相信对你通关面试、拿下Offer进入心心念念的公司有所帮助。
不好意思!耽误你的十分钟,JVM内存布局还给你
|
2月前
|
存储 安全 Linux
将文件映射到内存,像数组一样访问
将文件映射到内存,像数组一样访问
31 0
|
3月前
|
缓存 Java 编译器
Go 中的内存布局和分配原理
Go 中的内存布局和分配原理
|
2月前
crash —— 获取物理内存布局信息
crash —— 获取物理内存布局信息
|
3月前
|
存储 编译器 C++
Method&ConstMethod的内存布局
综上所述,常规方法和常量方法在对象的内存布局中并不直接占据空间;它们作为代码的一部分存储在程序的代码段中。对于虚方法(包括常量虚方法),它们通过VTable在对象中有表示,但即便在这种情况下,方法代码本身也不在对象的内存布局中。理解这些概念有助于深入理解面向对象编程,提高编程效率和代码的可理解性。
37 3
|
4月前
|
存储 缓存 算法
(五)JVM成神路之对象内存布局、分配过程、从生至死历程、强弱软虚引用全面剖析
在上篇文章中曾详细谈到了JVM的内存区域,其中也曾提及了:Java程序运行过程中,绝大部分创建的对象都会被分配在堆空间内。而本篇文章则会站在对象实例的角度,阐述一个Java对象从生到死的历程、Java对象在内存中的布局以及对象引用类型。
125 8