【Linux C】数据类型和存储才是C语言的精髓

【Linux C】数据类型和存储才是C语言的精髓,第1张

目录

变量的本质

数据类型和类型转换

有符号数和无符号数、数据溢出

定义和声明的区别

程序、程序文件模块和函数之间的关系

局部变量、全局变量、外部变量、静态变量、作用域、生命周期

内存中的段(section)


变量的本质

这里要说的变量既有普通变量,也有指针变量(被我们常常简称为指针)。


先说普通变量:

int a = 10;

如定义一个整型的变量a,初始值为10。


这句话给出了三个信息,分别是变量类型、变量名和变量值,这三个信息分别和存储大小、存储地址和存储内容一一对应。


 变量名的本质就是内存空间的别名,通过变量名可以直接对这段内存进行读写。


定义变量的目的就是方便对存储在内存中的数据进行读写,不是直接通过地址,而是通过变量名来访问内存。


编译器根据我们定义的变量类型,会在内存中分配合适大小的存储空间和地址对齐。


再说指针变量:

指针是指针变量的简称,本质上也是一个变量,和普通变量并无本质区别,只不过指针变量中存储的是地址。


那既然是变量,就有对应的变量类型。


除了普通指针外,指针变量还有函数指针(变量)、数组指针(变量)。


普通指针、函数指针和数组指针之间并没有本质区别,区别在于指针指向的东西是什么。


那既然所有的指针变量类型本质都是一样的,那为什么在C语言中还要去区分它们?主要原因是不同的写法可以给编译器提供不同的消息。


写代码的时候把自己当成编译器。


复杂表达式的解析方法:第1步找核心,第2步找结合(需要知道符号的优先级,其中() > [] > *  )

变量变量类型对应的存储大小
普通变量int aint不同平台int所占字节数不同
(普通)指针变量int *pint *对32位系统,指针总是占4个字节;64位系统是8字节
函数指针(变量)void (*pFunc)(void)void (*) ()
数组指针(变量)int (*p)[5]int (*p)[ ]

 为什么需要指针?

内存一般可分为静态内存和动态内存,一个程序被加载到内存运行时,代码段和数据段就属于静态内存,而堆栈则属于动态内存。


 静态内存的特点:内存中各个变量的地址在编译期间就确定了,在程序运行期间不再改变,因此可以通过变量名直接访问。


 动态内存的特点:变量的地址在程序运行期间是不固定的,如函数的局部变量。


 栈:在函数调用过程中,分配的栈帧空间地址不同,但局部变量在函数栈帧内相对于栈帧指针的相对偏移不会改变; 堆:不仅动态变化,而且还是匿名访问,无法借助变量名或栈指针来访问,只能使用指针来间接访问 。


指针设计的初衷就是访问一片匿名的动态内存。


指针难以理解和掌握,为什么在编程中还要使用指针呢?  

  • 原因1:有些地方不得不使用指针,没有别的备选方案。


    如动态堆内存的匿名访问 

  • 原因2:使用指针更容易编写出高质量高效率的程序。


    如参数传递、函数的返回值。


     

  • 原因3:一些链表、树等动态数据结构的实现也离不开指针,还有一些字符串指针、函数指针等 

指针和函数传参(指针的常用技巧)

普通传参、传递地址、传递数组、传递结构体

“地址值传递”和“普通值传递”都是值传递,只是传递指针时,被传值比较特殊,是一个地址,具有指向某个空间的作用。


为了提高数组的传递效率,传递的都是数组首元素首字节地址,只有4个字节大小,效率非常高。


传递数组时,形参的写法有两种,一种是直接写成指针变量的形式,另一种是“类型 名称[]”,后一种写法是为了提高可辨识性。


结构体传递有4中传参方式:

  • 成员值传递:传递结构体成员值,就是普通值传递
  • 成员地址传递:传递结构体成员的地址,其实就是地址传递
  • 传递整个结构体
  • 结构体的地址传递:提高传参效率
数据类型和类型转换

数据类型分为基本数据类型和构造数据类型。


基本数据类型有:short, int, long, float, double, char;构造数据类型有:struct, union, enum。


类型对编译器来说,主要就是用于说明数据存储空间的大小以及数据的存储结构。


当处理器对两个数进行算术运算时,一般要求两个数的类型、大小、存储方式都相同,这是由CPU的硬件电路特性决定的。


类型转换分为隐式转换和显式转换,需要注意的是对普通变量还是指针变量。


对于指针变量的类型转换,一律要求显式强制类型转换,主要设计对指向空间的强制类型转换和对指针变量本身的强制类型转换。


有符号数和无符号数、数据溢出

一个存储在物理内存中的数据,可以被看做一个有符号数,也可以被看做一个无符号数,关键看如何去解析它,它在内存里就是一串二进制数据0和1。


原码、反码和补码:

对于无符号数,原码、反码和补码是一样的;对于有符号数,采用补码形式存储,补码等于反码加1。


每一种数据类型都有它能表示的数值范围。


如signed char表示[128,127],unsigned char表示[0,255]。


一般来说,无符号数溢出会进行取模运算,继续周期轮回,使程序陷入死循环。


#include 
int main()
{
    unsigned char c = 255;
    printf("c = %u\n", c);
    c++;
    printf("c = %u\n", c);
    return 0;
}

如何防止数据溢出?

有符号数:两个整数相加小于0或两个负数相加大于0,说明溢出了;

无符号数:两个数的和小于其中任何一个加数,说明溢出了。


 

定义和声明的区别

区别一:作用阶段不同:编写的代码需要经过预处理、编译、汇编、链接等步骤得到可执行文件,声明作用于编译阶段、定义作用在链接阶段;

区别二:定义时分配存储单元、声明时不分配存储单元。


程序、程序文件模块和函数之间的关系

局部变量、全局变量、外部变量、静态变量、作用域、生命周期
  • 局部变量:定义在函数内部的变量,形参是局部变量。


  • 全局变量:定义在函数外不属于任何函数的变量,作用范围是从定义开始,到程序所在文件结束,只在某一个程序文件模块中有效。


  • 外部变量extern:当程序包含多个程序文件模块时,通过外部变量声明可以将全局变量的作用范围扩展到其他文件模块。


  • 静态全局变量static:将变量的作用范围仅限于当前的文件模块中,即使其他文件模块使用外部变量声明也不能使用该变量。


  • 静态局部变量:延长了生命周期(但也只是限于本文件模块),并没有改变作用范围(只在函数内部)。


作用范围和声生命周期是两个完全的概念。


内存中的段(section)
  • 代码段(.text):函数
  • 数据段(.data):初始化的全局变量、静态局部变量
  • .bss段 :未初始化的全局变量
  • .rodata段:字符串、字符串常量

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/569670.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-09
下一篇 2022-04-09

发表评论

登录后才能评论

评论列表(0条)

保存