c语言语法概论与内存管理
人们常说c语言灵活,灵活,但是学习c语言的时候总觉得好像那里灵活了,灵活了个啥,只感觉c语言很简陋,也做不出来啥东西,所以为啥大家总说c生万物,这在我心里一直是一个问题,今天我们来讨论学习一下,试着理解计算机知识中这沧海一粟,解决我们心中的困惑。
OK我们先从c语言的一些语法入手,开始我们的学习之路
c语言语法概论
c语言诞生之初就肩负起一个使命——管理内存。
当然普及一下基础知识
-
CPU:负责执行程序指令,进行逻辑运算和数据处理,是系统的“大脑”。
-
内存:临时存储运行中的程序和数据,供CPU快速访问,是CPU的“工作台”。
c语言可以通过指令直接对CPU下命令对内存进行操作,这是的CPU就宛如c语言的玩具,一举一动都由c语言操作。
早在1969年c语言就已经出现,由一帮技术大牛所掌控,给帮技术大牛对c语言的要求就是要十分的灵活,各种换着花样的写程序,像什么驱动程序,操作系统都是由这帮大牛写出来的。
但是,随着时间的推移,程序员的越来越多,菜是原罪,c语言的灵活性此时也显露出弊端,内存泄露导致的程序崩溃,人们为了更好的去使编程简单化对内存进行封装,就有了一系列的其他语言 c++ Java Python.
所以我们说c生万物。
话说回来,由此我们明白了学习数据结构意义——分配一定的空间,设计成一定的结构,为了满足高效存储,或者解决某种问题的高效方法。
好的首先我们来看如何分配空间
如何分配空间
大家不管学习什么语言都会发现每个语言都由自己的数据类型。
那么数据类型是什么呢?
从本质上数据类型就是一个模子,他对内存空间进行分配,宛如对一坨面(内存)进行分配(模子)。
软件为了更加方便的分配内存就提出了能否将多个字节分配使用,于是数据类型应运而生。
而不同的容量对应着不同的数据类型。
由此基本数据类型就诞生了
基本数据类型提供一套模具,分配内存使用,而它的核心就是数据类型的容量,它是8个字节还是4个字节
形象点就是厨房里的基础厨具,菜刀,小刀,锅碗瓢盆,而它们的任务就是去做菜(分配内存)。
所以我们就能知道
int a;//从本质而言a就是在内存上划分一片空间
a=10;//而当我们访问a时本质上就是访问我们划分给a的空间
//从而我们得出结论,在编译器眼里a只是个代名词,访问的只是a所对应的空间
那为啥不能一个字节一个字节的访问呢?
因为不够
一个字节8个bit换算成数字就是255,万一你要存个365那不就不够用了?
就算存进去了也会发生截断只能存255。
那么那么多数据我们如何判读大小呢?
我们就需要一把尺子——sizeof
这不是函数,这不是函数,这是一个关键字,是由编译器直接实现的。
void text() {
int a;
printf("%zd\n", sizeof(a));
}
int main() {
text();//会显示出4或8
return 0;
}
%zd是为了自动匹配,众所周知我们的系统分为32位系统和64位系统,不同的系统尺子所对应的刻度不一样。
普及一下
-
32位系统:像一条 四车道高速公路,一次只能通过四辆车(处理 4字节 的数据)。
-
64位系统:像一条 八车道高速公路,一次能通过八辆车(处理 8字节 的数据)。
所以不同电脑所处理的刻度不同,显示的字节的大小也不相同。
好,继续讲数据类型
不同的数据类型对应不同的字节
但是有点恶心的事情发生了
c90的定义中
char<short<=int<=long<=longlong
这种定义直接就导致在不同的系统上,分配的大小不同,比如有些系统上 int是4 long也是4,等等
所以如果遇见sizeof(int/long)我们应该回答什么?
不同的电脑上大小不同╮(╯▽╰)╭
那又出现一个问题——为啥我们在定义一个数据类型的时候先用int而不是char?
明明这样更省空间呀!
其实这其中有一个重要的原因就是为了更加的有效率计算机在读数据的时候int是一个周期效率是最高的,这个思想就是我们计算机中的一个基本思想,以时间换空间,以空间换时间。
关于整型变量
-
给内存空间起⼀个可以为程序⽅便访问的名字
-
⼤容量为⼩容量赋值的情况
截断
-
⼩容量为⼤容量赋值的情况
补位,如果是有符号数,补符号位,如果是⽆符号数,补0
原码 反码 补码
原码就是二进制 反码就是原码0换1 1换0 补码就是反码加1
而在计算机存储是补码
举个例子
-2
原码 1000 0010
反码 1111 1101
补码 1111 1110
我们来验证一下
void text() {
char a=-2;
printf("%hhx,%hhd,%hhu\n", a,a,a);
}
//hhx是16进制存储表示
//hhd是负全值的表示
//hhu是正全值的表示
int main() {
text();
return 0;
}
很奇怪答案是
那电脑又如何从补码推到原码的呢?
肯定不是像我们人一样,而是最高位是负权值,后面的位数都是正全值
向上面的这个补码就可以写成
-128+126,最后得出负二 而后面的126是后面的0111 1110换算出来的
而我们用hhu是用正全值的表示自然
就算出128+126=254这样的大值了
结构体
struct 不是数据类型,⽽是⾃定义成员组合⾏为的说明符
相当于自己创建了一个模具,C语言编译时,提前告诉模具的名字,使用这个模具,先声明后定义使用。
如下
struct abc
{
char a;
int b;
short c;
};
struct abc x1;//定义了一块空间
x1.a;//访问特定的空间
struct abc*p;
p->c;//本质上是指向一个所对应空间
共用体
大家公用一个地址,想要某个值就偏移到某个位置
共用体从本质上是给我们的空间按名字访问的一种方式
内存管理
内存如何找到,有两种方法,一种是叫它的名字,另一个就是叫它的地址,而c语言提供给我们寻找地址的方法就是大名鼎鼎的——指针
但是如果仅仅找个盒子将地址放进去,好像和存数据没有什么区别,对于指针我们更关心两点。
-
该数据类型的容量⼤⼩
-
这个指针指向空间的操作⾏为,即解决 p + 1和 p + 2,C语⾔编译器如何翻译
由此我们可以推出,对于指针相加读的内存就是数据类型的大小。
我们来证明一下
int main() {
int *p1 = (int *)0x100;
char *p2 = (char *)0x100;
char p3[5];
char *p4[5];
char (*p5)[5] = (char (*)[5])0x100;
char p6[6][5];
printf("the p1: %p, p1 + 1: %p\n", p1, p1 + 1);//0x100 0x104
printf("the p2: %p, p2 + 1: %p\n", p2, p2 + 1);//0x100 0x101
printf("the p3: %p, p3 + 1: %p\n", p3, p3 + 1);//0x??? 0X???+1
printf("the p4: %p, p4 + 1: %p\n", p4, p4 + 1);//0x??? 0x???+8
printf("the p5: %p, p5 + 1: %p\n", p5, p5 + 1);//0x100 0x105
printf("the p6: %p, p6 + 1: %p\n", p6, p6 + 1);//0x??? 0x???+5
return 0;
}
看下结果
由此我们明白了指针本身是对内存进行管理的一种方式,对它加减改变的大小与数据类型的大小相关。
关于函数指针
从本质上而言指针函数就是对函数的位置进行了储存
int (*add)(const char*,...)=printf;
//add首先要是一个指针来存放地址,后面的是函数的参数
int (*arr)(const char *,...)=printf;
arr("hello world");
printf("hello world");
都可以打印出来
它们两个的不同点在于,一个可以更换对象,而另一个不可以
arr可以等于其他的函数,而printf是已经固定死的不能被更改的
arr=xxx//xxx是函数名
要记住函数也可以当做参数去传,这个函数还有一个名字叫做——回调函数
OK现在我们回过头来看看数组
要记住数组不是数据类型
数组是为了表示多个同类型的连续空间,构造了⼀个数组结构语法糖
在c语言中一开始是没有数组的,只是为了后面使用方便而加的
int a[3];
a[3]//[]符号是偏移的意思 以a为首偏移三个单位看里面的值
数组名的含义:
-
实际是⼀个常量指针,仍然具备指针的运算能⼒具有特殊的sizeof效果
-
数组名的地址是⾮法的,但被编译器给重定义了
数组⽆法约束越界的问题,这也是C被诟病最多的地⽅
普通数组和字符数组的区别
字符串也是⼀个语法糖
-
双引号的功劳
-
⾃动加了⼀个’\0’符号
字符数组的初始化
-
普通逐个初始化
-
字符串的初始化
遍历算法的写法
-
普通连续空间
-
字符连续空间
内存的分段管理
我们写的程序就在一段虚拟内存上,在虚拟内存上进行分段管理 分配如下
如果你访问的不该访问的区就会直接报错
int main() {
char*s=NULL;
char a=*s;
printf("hello");
return 0;
}
代码段.text
只读数据段.rodata
数据段.data
-
static区 如果用了这个关键词就变为静态区
-
全局区
堆段
栈段
好的这篇文章就到这里,是不是由所收获呢,如果有记得点赞哦!(╹▽╹)