本节关键字
内核空间,栈,堆,数据段,代码段,不可访问区
常量,变量,数据类型,整型,实型,布尔型,字符型,无类型,指针类型
组合数据类型:结构体,共用体,枚举
运算符,算数运算符,关系运算符,逻辑运算符,位运算符,特殊运算符,运算符优先级
一、内存管理
1. 内存管理概述
学习C语言入门,主要看是否搞清楚了内存分配,因为从定义一个变量开始,就已经涉及了到内存的申请,如果申请内存后不及时释放,就会导致内存泄漏,最终使得程序停止运行。因此,学习C语言,其实就是学习内存管理。
2. Linux内存空间分布
为什么学习Linux系统的内存空间,而不是windows或mac操作系统下的?最主要的原因是Linux操作系统是开源的,网上相关的资料也比较多,学习Linux内存管理足够我们去理解C语言的内存管理。
下图是Linux内存空间的结构图:
图1.1 Linux内存管理
序号 | 分区 | 释义 |
1 | 内核空间 | kernel,Linux操作系统的核心 |
2 | 栈 | 说明:系统自动分配的空间,最大一般是8MB大小 操作方法:后进先出 举例:环境变量、命令行参数、局部变量(调用时申请占空间,使用完毕立刻释放空间) |
3 | 堆 | 说明:用户申请的空间,最大值取决于系统的物理内存 举例:malloc、calloc、realloc、free分配和释放的内存 |
4 | 数据段 | .bss段 :存放未初始化的静态数据,它们都将被初始化为0 , 举例:全局变量 ,static修饰的局部变量 .data段:存放已初始化的静态数据 ,初始值从程序文件中拷贝而来 举例:全局变量 ,static修饰的局部变量 .rodata段:常量 |
5 | 代码段 | .text段:存放用户程序代码,包括main函数在内的所有用户自定义函数 .init段:存储每一个程序执行前的“初始化”代码 |
6 | 不可访问区 |
<1>栈&堆
栈和堆都是动态变化的,分别向下和向上增长,大小随着进程的运行不断变大变小。
<2>全局变量&局部变量
局部变量存放在栈空间,用在功能函数中,在函数执行完毕后会被立即释放,再次调用函数时会重新定义该局部变量,系统会根据当前的内存空间进行动态分配,局部变量所存放的地址也肯定会改变;
而全局变量被存放在数据段的静态存储区(.bss/.data),直到整个进程结束时,全局变量所占的空间才会被释放掉,在整个进程里全局变量的存放地址是恒定的。
<3>静态数据
静态数据指的是:所有的全局变量,以及 static型局部变量(存放地址不会改变,直到进程结束才释放资源)。
尽量避免使用静态数据,因为它所占用的空间后直到进程结束才会被释放掉,程序运行期间在没有用到该静态数据时,它会无条件一直占用共享资源(内存)。
<4>环境变量、命令行参数
每一个程序运行前都需要有一段初始化代码,这些代码就包含了环境变量的准备,命令行参数的组织和传递,它们存放在栈空间的底部(紧挨内核的地方)。且在进程在整个运行期间不再发生变化,假如进程运行时对环境变量的个数或者值做了修改,则为了能够容纳修改后的内容,新的环境变量将会被拷贝放置到堆中。
图1.2 栈区的内存分配
3. 堆空间操作函数
堆空间是唯一一块用户可以自主定义的空间,下面介绍堆空间的申请和释放函数:
头文件:#include<stdlib.h>
<1>malloc申请一块堆空间
函数定义 | void *malloc(size_t size); |
参数 | malloc(内存大小(字节)) |
功能 | 在堆中申请一块大小为size的连续的内存,申请的内存是未初始化的 |
返回值 | 成功,新申请的内存基地址 ;失败,NULL |
<2>calloc申请多块堆空间
函数定义 | void *calloc(size_t n, size_t size); |
参数 | calloc(块数,每块内存大小(字节)) |
功能 | 在堆中申请一个具有n个元素的匿名数组,每个元素大小为size,该函数申请的内存将被初始化为0 |
返回值 | 成功,新申请的内存基地址 ;失败,NULL |
<3>realloc放大堆空间
函数定义 | void *realloc(void*ptr, size_t size); |
参数 | realloc(堆内存地址,内存大小(字节)) |
功能 | 将ptr所指向的堆内存大小扩展为size (1)返回的基地址可能跟原地址 ptr相同,也可能不同(即发生了迁移) (2)当size为0时,该函数相当于相当于free(ptr); |
返回值 | 成功,扩展后内存的基地址 ;失败,NULL |
<4>free释放堆空间
函数定义 | void free(void *ptr); |
参数 | free(堆内存地址) |
功能 | 将指针ptr所指向的堆内存释放 参数ptr 必须是malloc( )/calloc( )/realloc()的返回值 |
返回值 | 无 |
二、数据类型
编程初期我们必须要定义一个对象(申请对象的空间),对这个对象进行操作,才能实现对应的功能,例如定义一个对象:
int a ; //定义一个int类型的对象a
这里的“int”就是所谓的数据类型,“a”就是对象,它可以是常量或者变量。
1. 常量
存放在数据段的.rodata段,用const关键字修饰,一旦初始化(定义+赋值)后,其值不能改变,例如:
const int a = 10 ; //定义一个int型的常量a,并给它赋值为10
常量包括整形常量,实型常量,字符常量和字符串常量。
2. 变量
如同它的名字,这是一个在初始化后仍可以随时再次改变数值的对象,例如:
int a = 10 ; //定义一个int型的变量a,并把它赋值为10
a = 20 ; //把变量a的值修改为20
变量按类型可以分为整形变量,实型变量等;按作用范围又可以分为局部变量和全局变量,其中局部变量存放在栈区,全局变量存放在数据段的静态数据区。
3. 常见的数据类型
(1)整型
数据类型 | 释义 | 大小 | 备注 |
int | 整型(一切整数) | 4byte | |
short | 短整型 | 2byte | 大小是int的一半 |
long | 长整型 | 8byte | 大小是int的两倍 |
(2)实型
数据类型 | 释义 | 大小 | 备注 |
float | 实型(浮点型,一切实数,包括小数) 精确到小数点后六位 | 4byte | 1.23e4 = 1.23×10^4 |
double | 双精度实型 | 8byte | 精度是float的两倍 |
long double | 长双精度实型 | 16byte | 精度是double的两倍 |
(3)布尔型
数据类型 | 释义 | 大小 | 备注 |
bool | 布尔型(真(非0)、假(0),ture or false) | 1byte | #include<stdbool.h> |
(4)字符型
数据类型 | 释义 | 大小 | 备注 |
char | 字符型 | 1byte |
(5)无类型
数据类型 | 释义 | 大小 | 备注 |
void | 无类型 | 1byte |
(6)指针类型
数据类型 | 释义 | 大小 | 备注 |
int * | 普通指针类型(还包括short *,long *,float *,double *,bool *等等) | 8byte | |
void * | 万能指针类型 | 8byte |
验证代码
<1>验证数据类型大小的代码
#include <stdio.h>
#include <stdbool.h>
int main()
{
printf("int:%ld字节\n",sizeof(int)) ;
printf("short:%ld字节\n",sizeof(short)) ;
printf("long:%ld字节\n",sizeof(long)) ;
printf("float:%ld字节\n",sizeof(float)) ;
printf("double:%ld字节\n",sizeof(double)) ;
printf("long double:%ld字节\n",sizeof(long double)) ;
printf("bool:%ld字节\n",sizeof(bool)) ;
printf("char:%ld字节\n",sizeof(char)) ;
printf("void:%ld字节\n",sizeof(void)) ;
return 0 ;
}
验证结果:
<2>验证指针类型的大小
#include <stdio.h>
#include <stdbool.h>
int main()
{
printf("int *:%ld字节\n",sizeof(int *)) ;
printf("short *:%ld字节\n",sizeof(short *)) ;
printf("long *:%ld字节\n",sizeof(long *)) ;
printf("float *:%ld字节\n",sizeof(float *)) ;
printf("double *:%ld字节\n",sizeof(double *)) ;
printf("long double *:%ld字节\n",sizeof(long double *)) ;
printf("bool *:%ld字节\n",sizeof(bool *)) ;
printf("char *:%ld字节\n",sizeof(char *)) ;
printf("void *:%ld字节\n",sizeof(void *)) ;
return 0 ;
}
验证结果:
由此可见,所有指针类型大小都是8个字节(64位),而在32系统指针类型的大小是4个字节。
三、组合数据类型
组合数据类型,顾名思义,就是把一些相同类型或者不同类型的对象放到一起。
1. 结构体struct
<1>结构体练习
任务:定义一个学生结构体模板,再根据这个结构体模板生成两个结构体变量,分别存放两个学生的信息,并打印出来。
#include <stdio.h>
#include <string.h>
//定义一个模板结构体(不能赋值)
struct template
{
char name[10] ; //姓名
int age ; //年龄
float height ; //身高
};
int main()
{
//利用上面的模板结构体template还可以定义新的结构体变量
struct template stu1 ; //根据template结构体衍生出stu1结构体
struct template stu2 ; //根据template结构体衍生出stu2结构体
//给新的结构体的成员赋值
strcpy(stu1.name,"小明") ;
stu1.age = 10 ;
stu1.height = 162 ;
strcpy(stu2.name,"小红") ;
stu2.age = 9 ;
stu2.height = 151 ;
//打印验证是否成功给新的结构体赋值
printf("——————————————————————————————————————\n") ;
printf("姓名:%s\n",stu1.name) ;
printf("年龄:%d\n",stu1.age) ;
printf("身高:%f\n",stu1.height) ;
printf("——————————————————————————————————————\n") ;
printf("姓名:%s\n",stu2.name) ;
printf("年龄:%d\n",stu2.age) ;
printf("身高:%f\n",stu2.height) ;
return 0 ;
}
运行结果:
<2>计算结构体的大小
结构体的大小,取决与结构体成员中类型最大的那一个,类型最大的结构体成员是结构体的存储单位。比如求下图的结构体大小:
图3.1 结构体类型大小
上图的template模板结构体中,double是最大的类型,所以结构体的存储单位是8byte,值得注意的是,虽然name成员的类型最小(char类型),但是却占10byte!接下来就是给结构体成员分配空间(根据成员从上往下的顺序依次分配):
图3.2 结构体空间初分配
图中的绿框表示结构体的存储单位8byte,可以看到,name超出了2byte,age、sex、height都没能填满,money正好填满(因为就是以它为单位的)。
图3.4 结构体空间分配的结果
name超出去的部分怎么办?没关系,系统会进行动态分配,可以看到第2格有足够的空间,于是多出去的2byte就被分配到第2格里了,同时第5格的height也会被优化到第4格,以节省空间。
于是就可以算出结构体的大小为:8×4=32byte
最节省空间的方法是先把结构体成员进行从小到大排序。
验证代码
#include <stdio.h>
#include <string.h>
//定义一个模板结构体(不能赋值)
struct template
{
char name[10] ; //姓名
int age ; //年龄
double money ; //钱
char sex ; //性别
float height ; //身高
};
int main()
{
//利用上面的模板结构体template还可以定义新的结构体变量
struct template stu1 ; //根据template结构体衍生出stu1结构体
printf("stu1结构体的大小:%ld\n",sizeof(stu1)) ;
return 0 ;
}
运行结果:
2. 共用体union
共用体也称联合体,与结构体类似,不同的是成员空间的分配方法。在结构体中,每个成员都根据自己的实际大小独占一份空间,而在共用体中,是所有成员共享一份空间,这个空间的大小是共用体中最大成员的大小。结构体中的所有成员在任何时刻都能够使用,而共用体在某一时刻只能使用一个成员。
<1>共用体练习
任务:定义一个共用体,并计算其大小。
#include <stdio.h>
#include <string.h>
//定义一个模板共用体(不能赋值)
union template
{
char name[10] ; //姓名
int age ; //年龄
float height ; //身高
}x;
int main()
{
//利用上面的模板共用体template还可以定义新的共用体变量
union template stu1 ; //根据template共用体衍生出stu1共用体
printf("stu1共用体的大小:%ld\n",sizeof(stu1)) ;
printf("%ld\n",sizeof(x)) ; //x表示共用体的大小
return 0 ;
}
运行结果:
<2>计算共用体的大小
和结构体一样,共用体的大小也取决与共用体成员中类型最大的那一个,类型最大的成员是共用体的存储单位。比如计算下图中共用体的大小:
图3.5 计算共用体的大小
共用体中类型最大的是int和float都是4byte,所以共用体的存储单位是4byte,但是共用体中最大的成员name大小为10byte,共需要3个4byte空间存储,所以该共用体的大小为:4×3=12byte。(具体验证代码见上面)。
3. 枚举enum
枚举像一组宏定义,可以增强程序的可读性。如果枚举常量列表中的常量没有赋值,则默认从0开始递增。
任务:完成枚举应用的练习。
#include <stdio.h>
//定义一个枚举常量列表模板
enum template{red=1,green=2,blue=3} ;
int main()
{
//利用上面的枚举常量列表模板template,定义新的枚举常量列表变量
enum template color ;
color = blue ; //使用列表成员blue
switch(color)
{
case red: printf("是红色\n") ; break ;
case green: printf("是绿色\n") ; break ;
case blue: printf("是蓝色\n") ; break ;
}
printf("枚举常量列表color的大小:%ld\n",sizeof(color)) ;
return 0 ;
}
运行结果:
四、运算符
在编写代码时,我们经常会有一些计算方面的需求,下面罗列了C语言中常用的运算符和优先级排序。
1. 算数运算符
2. 关系运算符
3. 逻辑运算符
4. 位运算符
5. 特殊运算符
运算符 | 释义 |
= | 赋值运算符 |
+= | 复合赋值运算符,类似的还有-=,*=,/=,等等 |
( ) ? : | 三目运算符,x=(a>b) ?a: b; //如果a>b,则x=a ;否则x=b |
( , ) | 逗号运算符,比如有这个语句: int x =(a++,b+=1, a+b);则先运算a++,再运算b+=1,再把a+b的值作为整个逗号表达式的值赋给变量x。 |
6. 运算符优先级
五、随机数
1. rand
定义 | rand () |
头文件 | #include <stdlib.h> |
注意:
(1)函数rand () 所产生的随机数是一个伪随机数,在每次执行程序时所产生的随机数都是一样的。
(2)调用函数rand () 生成的是一个在 0~32767 之间的整数。
控制计算机生成的随机数的取值范围的方法如下:
(1)利用 求余 运算rand ()%b 将函数rand ()生成的随机数变化到 [ 0,b-1 ] 之间。
(2)利用rand ()%b + a 将随机数的取值范围平移到[ a,a+b-1 ] 之间。
2. srand
定义 | srand () |
头文件 |
srand () 函数可以设置 随机数种子,从而使程序每次运行时产生不同的 随机数序列。
定义 | srand (time(NULL)) ; |
头文件 | #include <time.h> |
函数time () 可读取计算机的时钟值,并把该值设置为随机数种子。