如有错误,感谢指正,请私信博主,有辛苦红包,拜“一字之师”。
多看手册!!!
写代码的过程
编写: 程序员写代码的过程 让程序员看懂 (.c)
编译器:查看代码的语法错误,生成汇编语言。(.obj)
汇编器:将生成的汇编语言 生成二进制语言(目标文件)
连接器:将生成好 二进制语言+用到的库+启动代码 ====》可执行文件.exe
进制转换:本文不再讲解。
二进制 八进制 十进制 十六进制 都是整型的输出形式 不同进制 仅仅是数据的表现形式 不会修改数据本身
二进制0-1
八进制0-7
十进制0-9
十六进制 0-9 A-F
一些小应用
%p 输出地址
%d 十进制整形输出 有符号int数据输出
%o 八进制
%x 十六进制
%ld 有符号 long整型数据输出
%lu 无符号 long整型数据输出
%hd 有符号 short数据输出
%hu 无符号 short 数据输出
%c字符输出 单个字符
%s 就是输出字符串
%f输出float数据 scanf(“%f”, &f);
%lf 输出double数据 scanf(“%lf”, &d);
%u 无符号整型输出 %p 指针的值
浮点数的特殊表示
%5d 表示占5个终端位宽右对齐
%-5d 表示占5个终端位宽左对齐
%05d 表示五个位宽右对齐,空的补0 // %-05d 不能这样写 不起作用 会有歧义
%5.2f 5表示总位宽 .2 表示小数位保留2位
%.2f 表示 小数点后保留两位
数据类型关键字 12个 char short int long float double unsigned signed struct union enum void
控制语句关键字 12个 if else switch case default for do while break continue goto return
关键字4个 auto extern register static const
其他关键字 3个 sizeof typedef volatile
保留字未记录
区分关键字的原因
给用户合理分配利用空间
一个字节 bit=8b位二进制 0000 0000 — 1111 1111 1KB=1024B (1MB 1GB 1TB 1EB)
32位平台
char 字符型 1个字节 short 短整型 2个字节 int 整型 4个字节 long 长整型 4个字节
long long 有的支持 有的不支持 8字节 float 单精度浮点型 四字节
double 双精度浮点型 8字节
案例 验证数据类型长度 sizeof:测量类型的长度
sizeof有三种语法形式,如下:
#include
void main(){
void main() {
int a;
short char b;
unsigned u;
char e,f,g;
long int c;
e = sizeof(a);//b,c,u,e
printf("sizeof(a)=%d",e);
f = sizeof(int);
printf("sizeof(int)=%d",f);
g = sizeof int;
printf("g=%d",g);
printf("sizeof int=%d",sizeof(int));
}
64位平台
long型占8字节 其他都一样
unsigned 无符号数 数据没有符号位 自身所有二进制位都是数据位
signed 有符号数 默认一般省略 二进制最高位为符号位 其他位为数据位。 最高位为1表示负数 0表示正数
signed char 范围 1111 1111 -1000 0000 ~ 0000 0000 -0111 1111 /-10 == 1000 1010
结构体struct和公共体union
struct 结构体中的成员拥有独立空间
union 共用体中的成员 共享同一份空间
enum 枚举 将变量和要赋的值一一列举出来
enum BOOL {false,true};
enum BOOL bool = false;
void 无类型(重要)
不能用 void定义变量
auto 自动类型 /c中地位很低 c++ 有用
register 寄存器变量 如果没有表面register 但被高频繁使用 系统也会放入寄存器中
static 静态变量
const 只读变量
sizeof 测类型大小
typedef 为已有的类型 重新取个别名
volatile 防止编译器优化
1.为什么需要变量?
变量是程序组成的基本单位
变量的解释 相当于内存中一个数据存储变量的表示
常量 定义后值不能被修改(不能反着说) 整型 实型 字符串型 字符型
实型常量 123e3 e表示次方
不以f结尾的 实型常量 为double类型 float f=3.14f
以f结尾的 实型常量 为float类型
字符常量
直接常量 用单引号括起来的 ‘a’ ‘d’ 字符在计算器中存储的是ASCII值
char ch =‘a’ 本质上存储的是‘a’的ASCII值
转义字符 \开头的字符 \n 换行 \t tab制表符 \ 输出\ \0 ==》 0 “0” 字符串的地址编号 但这两个存储一样
赋0标准方式 c=’\0’
str %d ASCII值 %c str本身 字符串作为类型 代表字符串占空间大小 作为地址 代表str 首地址
系统会在字符串末自动添加一个\0 即 “a” 包括 ‘a’ 和 ‘\0’
‘ ’ 取字符串ASCII值 “” 取得是首元素地址
%s 从首元素开始 逐个输出\0结束输出
注:此处的str是指定义如 char str=abc;非str数据类型
变量 系统根据变量的类型 开辟对应空间 值可以被修改 变量名代表空间的内容 操作变量就是对空间内容的操作 先 声明 =》 再赋值 =》后使用
1.变量表示内存的一个存储区域(不同数据类型,占用大小不同)
2.该区域必须有自己的变量名和类型
3.变量必须先声明后使用
4.变量在同一作用域内不能重名
5.该区域的数据可以在同一范围内不断变化
6.变量三要素(变量名+值+数据类型)
7.不同编译器版本对变量定义的要求不一样,c89要求变量定义在语句之前
变量名的命名规则 不能以数字开头 由字母数字下划线构成
1.不允许用关键字
2.所有宏定义,枚举常数,常量(只读变量)全用大写字母用_分割单词
3.定义变量不要忘记初始化
4.变量名,方法/函数 名:小驼峰 大驼峰
/*
深入了解请看不同系统的开发手册,不一样的,跟底层硬件关系太紧密,不同硬件不一样,linux跟win差别挺大
基本类型
数值类型 默认都是有符号的signed和unsigned 大小都要范围,不要溢出,自己百度范围,不同系统不一样,越界值会变
整型 short
int
long 装不下了才用long long 等组合 输出用 %lld
浮点型 float
double
字符类型 char
C89没有定义Boolean
0表示False
非0表示True
构造类型
数组 arr
结构体struct
联合体union
枚举类型enum
指针类型
空类型 void
PS:1.C语言中没有string 用字符数组代替
2.不同位系统中相同数据类型的字节数可能不同(如:int 自行百度)
*/
浮点型
浮点型 两种写法
1.十进制 float=3.2
2.科学计数法 float=5.12e2(-2) // e表示多少次方
用精度较大的值 建议尽量用double
char类型
ASCII介绍
整型变量的操作 【读、写】 ==》赋值/取值-操作
局部变量不初始化 内容随机
scanf(“%d”,&data); &data代表是data对应空间的起始地址 只能提取一个字符 scanf(“%c”,&ch)
ch = getchar() 获取一个字符
typedef 为已有的类型重新 取个别名
1.用已有的类型 定义一个变量
2.用别名 替换 变量名
3.在整个表达式的前方加上typedef
语法结构:const 数据类型 常量名=常量值;
const 关键字
修饰变量 作用是 只读不能修改
作用范围 本文件全局 从声明之末尾 但不能跨文件 如果要跨文件 需要extern
但是 如果知道地址可以间接修改 不通过变量名
const int p
const 在左边 表示cosnt 修饰的是* 而不是p
效果:用户不能借助*p更改空间内容 但是p可以指向其他空间(*p只读 p可读可写)
int const p;*
const在右边 表示 const 修饰的是p
用户 可以通过p 修改p所指向的空间内容 但不能更改p的指向(*p可读可写 p只读)
*const int *const p;(p p 都是只读)
#define 名 值
写在最前面,用作替换
define和const区别
1.const定义常量时,带类型;define不带类型
2.const是在编译,运行的时候起作用;而define是在编译的预处理阶段起作用
3.define只是简单的替换,没有类型检查,简单的字符串替换会导致边界效应(想把后面当整体得加括号)
4.const变量可以进行调试,define是不能进行调试(编译阶段已替换,调试时没有它)
5.const不能重定义,不可以定义两个相同的常量;而define可以通过undef取消某个符号的定义,再重新定义
6.define可以结合#ifdef,#ifndef和#endif来使用,让代码更灵活
自动转换 占用内存少的类型转占内存多的/精度低的转精度高的
数据精度大小排序自行百度查询
有符号和无符号参加计算时 会把有符号转为无符号 (补码) 很大的数
int 和double 一起算会先把int转double
char 和 short 转换 因为char short自身字节数少 很容易溢出会自动转int 防止数据丢失
强制类型转换 :精度高的转精度低的
(类型说明符)(表达式) /只是临时转换 当前语句有效 不影响原值,返回新值
不是四舍五入 是直接截断
栗子:
float x = 3.94f;
int j =0;
j = (int)x; //3
只对最近的数有效 想要转更多用()
int num2=(int)(10.655.1+62.3)
int num3=(int)10.256545+564.34
算数运算符 + - * / % ++ -- 正负号
赋值运算符 = += -= *= /= <<= >>= &= |=
关系运算符(比较运算符) > < >= <= != ==
逻辑运算符 && || !
位运算符<< >> & | ^ ~
三目运算符 表达式? 表达式1 :表达式2
1.算数运算符 + - * /取整 %
/ 取整 a/b ab都是正数的时候 取整 除法“%f\n”,5/2.0f
% 取余
a % b = a - a / b *b
算数运算有可能数据损失,如:int 10/3=3而 float=3.333333~,提升数据精确度解决
++ – 运算符 ++i --i 先算后值 i-- i++ 先值后算
2.关系运算符 > < == >= <= !=
3.逻辑运算符! && || 注意短路现象
!逻辑非 0为假 其他都为真
短路现象 &&一假全假 遇假不再执行
|| 一真全真 遇真不再执行
4.位运算符 << >> & | ~ ^
&按位与 语法 全1为1 其他为0 和1相遇 保持不变 和0相遇 清零 // 底层清零用
| 按位或 语法 有1则1 全0为0 和0相或 保持不变 和1相或 置1 // 将固定位置1
~按位取反 语法 0变1 1变0 配合 & | 操作
^按位异或 语法 语法 相同为0 不同为1 与0异或保持不变 和1异或取反 // 将固定的位 发生高低电频翻转
如:
1010 1010
0000 1111
-----------
1010 0101
左移运算符<< 多的丢弃 少的补0 移动位数 不要超过自身的长度
右移运算符>> 溢出的丢弃
逻辑右移:右边丢弃 左边补零
算数右移:无符号数右边丢弃 左边补零
有符号数:
正数 右边丢弃 左边补0
负数 右边丢弃 左边补1
逻辑右移和算数右移 是编译器决定的 但是我们可以检测
data = data & ~(0x01 << 5 | 0x01 <<1); 清零操作 没有~是置1
5.赋值运算符 (= 及其扩展赋值运算符)
(a +=b )==( a = a + b)
6.三元运算符(?:)
一真大师/条件表达式为1(真)表达式1执行
可以转化为if-else语句
7.逗号运算符(,) 优先级最低
8.指针运算符(*和&)
9.求字节数运算符(sizeof(变量或常量))
10.强制类型转换运算符((变量或常量))
11.分量运算符(.->)
12.下标运算符(【】)
13.其他(如函数调用运算符();)
优先级 : 优先级高的先执行,不同优先级看结合性 自己写尽量加()
自己百度,记不住就加括号提高优先级,不需要刻意去记,多用就会了
算术运算符 > 关系运算符 > 逻辑运算符(逻辑非除外) > 赋值运算符 > 逗号运算符
同级中 .高于* 【】高于* 函数()高于* ==和高于位操作和赋值 算术运算符高于位运算符
1.只在乎项目的一个结果 选择if
if(表达式)
{
语句1;
}表达式为真 执行语句/代码块
*if-else语句 如果有两种不可能同时存在的情况*
if(表达式)
{
语句1;
}else
{
语句2;
} 表达式为真 执行语句1 为假 执行语句2
*if - else if -else语句 如果有多个结果且不同时出现*
if(表达式1)
{
语句1;
}else if(表达式2)
{
语句2;
}else if(表达式3)
{
语句3;
}
else // 可省略
{
语句n;
} 表达式几成立 执行语句几 满足哪个条件,执行哪个语句 按顺序 满足一个就跳出判断 不再看后面的
前面都不满足执行else的语句 每个if语句是独立的
switch(表达式)
{
case 值1: // 不能是实型,字符串 只能是整型数值
语句1;
break; //大多数都要break 少数不用break 比如当两种情况需要执行的代码块一样时 省略一对语句与break
case 值2: 值可以写|| 这种 不报错 但是可能跟自己相要的效果不一致
语句2;
break;
case 值3:
语句3;
break;
case 值4:
语句4;
break;
default;//可以省略
语句n;
break;
}与哪个值同 执行哪个下面的语句 break跳出 如果都不同 执行default下的语句然后跳出
for(初始语句;循环条件;步进条件)
{
循环语句
}
初始语句:循环开始时执行一次 // 如果变量提前初始化了 可以省略
循环条件:每次循环都要执行 循环条件为真 进入循环 为假退出循环 // 如果省略了 必须在循环语句中实现退出
步进条件: 每次循环结束时 要执行的语句 // 也可以省略 在循环语句里实现步进动作 否则就是死循环
break 只能跳出离它最近的一层循环
continue 推出单次循环 继续执行下次循环
while(循环条件)
{
循环语句;
} 条件为真 进入循环体 执行循环语句
while没有步进语句和初始化语句 需要提前初始化和写好退出循环的条件
do {
循环语句
}while(循环条件) ;
先执行循环语句 再判断条件 为真执行下次循环 为假 跳出循环
goto 跳转锚点
goto here;
here:
: 为了方便 将具有相同类型的若干变量按有序形式组织起来—成为数组
数组属于构造数据类型
按照元素不同 分为 数值数组 字符数组 指针数组 结构体数组
一维数值数组
定义:需求 请定义一个数组 该数组有10个元素 每个元素为int
在定义的时候 a.arr【】 arr和【】结合是数组
b.将确定的元素的个数放入【】中
c.用元素的类型定义一个普通变量
d.从上往下整体替换。
int arr【10】;
数组名arr不能和其他变量名重名
数组的元素下标是从0开始:0-9
数组的元素分别是:arr【0】、arr【1】~arr【9】访问数组【10】越界
数组的元素等价于普通变量
在定义数组的时候,【】里面的值 不能是变量。
局部数组如果不初始化 内容不确定
初始化 定义时给变量或数组元素赋值的动作叫初始化
全部初始化int arr=【5】={1,2,3,4,5}
部分初始化 未被初始化 部分自动补0
初始化操作常用操作 将数组所有元素清零 int arr【5】={0} // 值初始化arr【0】=0 其他补零
int arr【5】={2} // 2 0 0 0 0
扩展:初始化 int arr【5】={【2】=3,【4】=7}
数组占内存大小 靠内容撑开 = 元素的个数*每个元素的大小
重要事项 int n= sizeof(arr)/sizeof(arr[0]) 通用获取数组元素个数
二维数组 int arr[n][n];
二维数组 初始化 分段初始化int arr【3】【4】={{1,2,3,4},{5,6,7,8},{9,10,11,12}}
连续初始化:放满一行 才能放下一行int arr 【3】【4】={1,2,3,4,5,6,7,8,9,10,11,12}
字符数组 char arr[];
初始化 char str=[‘h’,‘l’,‘l’] 逐个初始化 不推荐
char str[6]=“hello”; 以字符串的形式 初始化 推荐
遍历 逐个遍历 不推荐 printf(“%c”,st[i])
整体遍历 推荐 printf(“%s\n”,str)
重要:一维数组名代表的是数组的第0个元素的地址,必须记住
逐个字符初始化和整体初始化的区别
逐个 是多少就是多少 系统不会认为是str
整体 最后会有个\0 系统认为是str
scanf和%s使用的时候,有个缺点,遇到空格会结束输入
gets(buf); 获取带空格的字符串 缺点:获取键盘输入的时候 不会管buf 的大小,容易造成内存污染
fgets函数 既可以获取带空格的字符串,也可以保证buf不越界
> char *fgets(char *s , int size ,FILE *stream) s表示存放字符串的空间地址 size
> 能够提取字符串的最大长度 size-1 最后一个存\0 stream stdin 表示标准输入设备 返回值 获取到的字符串的首元素地址
> char buf[10] = "";
printf("shuru")
fgets(buf,sizeof(buf),stdin);
printf("buf=%s\n",buf); 打包到一个函数
> ASCII 小写变大写 -32
二维字符数组 char str[2][6]={"","",""};
不管是数值还是字符的二维数组,在初始化的时候,可以省略行标(第一个参数)行数由具体初始化决定
第二个参数是该行大小
自定义函数
函数类型 函数名(形参类型 参数名)
{
数据定义部分;
执行语句部分;
}函数类型: 函数返回值类型 默认int型
函数定义:实现函数功能、确定函数体、返回值类型、形参类型。让函数存在 函数声明:不是实现函数功能 仅仅说明函数返回值类型、形参类型、函数名
函数调用:函数的执行
函数定义
返回值类型 函数名(形参类型 形参){
函数体;
}
返回值类型:函数将来返回值的类型
函数名:函数的入口地址
形参:函数外部数据 传递到 函数内部的 桥梁
函数体:具体的函数功能带
函数声明:返回值类型 函数名(形参类型 形参) 告诉编译器 函数存在 请通过编译
函数调用:函数名(实参);
实参:函数外部的实际数据
return;1.返回函数结构 2. 结束函数 严谨
如果函数的形参啥都不写 调用时可以写实参,只是实参得不到使用 阅读有误导 函数参数 如果函数没有参数 将形参写成void 高级编辑器
就不会允许调用的时候传参 低级的写参不报错 但不要写
参数传递 函数调用才给形参开空间
寄存器EA 存着 return返回值
!注意
函数的形参 本质是函数的局部变量
形参 在函数定义时不会开空间 在函数调用的时候才开空间
形参 在函数结束的时候 才被释放
函数返回值 <=4字节 存在寄存器 >4字节 存在栈区
extern 声明外部可用 麻烦 zhu.h .h会自动添加 #include “zhu.h”
头文件思想 header file
普通局部变量
静态局部变量
普通全局变量
静态全局变量
内存分区
可执行文件未被运行
bss段 全局未初始化数据 全局区
data段 全局初始化数据 全局区
text段 代码段 代码区
运行可执行文件//后面讲指针时,有对内存的深入讲解
堆区----可读可写 使用 malloc calloc realloc free动态申请
栈区----可读可写 局部结构体 函数形参
函数返回值>4字节 <4字节在寄存器 全局区—可读可写 全局变量 静态变量static
文字常量区–只读 字符串常量 符号常量
代码区—只读 二进制代码
普通局部变量
定义形式:在{(复合语句)}里面定义的普通变量 就是普通变量
作用范围:离它最近的{}里有效
生命周期:离它最近的{}之间有效,离开{}的局部变量 系统自动回收
存储区:栈区(栈区特点:先进后出 桶类型)
普通全局变量
定义形式:定义在函数外部的变量 就是普通全局变量 {(复合语句)}外
作用范围:a.当前源文件都有效
如果不赋值 加extern 容易当成定义不赋值 写了易读
b.其他源文件使用全局变量的时候 必须用extern声明
生命周期:整个进程都有效(程序结束的时候 全局变量才被释放)
存储区:全局区
注意:A.全局变量不初始化 内容为0 在全局区的bss段,会被自动补0
B.如果全局变量 要在其他源文件中使用,必须在所使用的yuanwej中加extern声明
C.如果全局与局部重名 优先使用局部变量
静态局部变量
定义形式:在{}中定义 前面必须加staic 修饰这样的变量叫静态局部变量
作用范围:离它最近的{}之间有效
生命周期:整个进程(程序结束的时候 静态局部变量才被释放)
存储区:全局区(全局区的生命周期是整个进程)
注意:A.静态局部变量不初始化,内容为0
B.只能被定义一次,空间不会被释放(重要)
静态全局变量
定义形式:在函数外边定义 同时加static 这样的变量就是 静态全局变量
作用范围:当前源文件有效 不能在其他源文件使用 // 好处 不同文件中不冲突,重复不报错
生命周期:整个进程(程序结束 静态全局变量才被释放)
存储区:全局区
注意事项:A.静态全局变量不初始化 内容为0
B.静态全局变量只在当前源文件有效
全局函数(普通函数)
特点:其他源文件可以使用全局函数 但是必须加extern声明
静态函数(局部函数)返回值前加static void ming(can){}
特点:只能在当前源文件使用,不能在其他源文件使用
如果想在其他源文件调静态函数 需要将静态函数封装在全局函数 同时全局函数和静态函数必须是同一个源文件
//集成开发环境看不到编译过程
预处理:头文件包含,宏替换,条件编译,删除注释,不做语法检查
编译:将预处理后的文件 生成 汇编文件 语法检查
汇编:将汇编文件 编译 二进制文件
链接:将众多的二进制文件+库+启动代码 生成 可执行文件
总结 一步到位 编译:gcc 源文件 -o 可执行文件
预处理:
1.文件包含#include <> ,""
“hehe.h” 先从源文件 所在的目录寻找 如果找不到 再到系统指定的目录下找 //用户包含 用户自定义的头文件
注意 千万不要包含 名.c 虽然不报错 很容易出错 重复包含有很多定义 会出错
2.宏定义 : #define 宏名 字符串
宏后面不要加;替换多了;会有语法错误 替换的动作叫 宏展开
宏定义只在当前文件起作用
不带参数的宏
带参数的宏
调用 #define 宏名 (参数1,参数2…)字符串
宏一般大小 和普通函数区分开
宏的参数 a b 不能写类型
调用: 宏名(参数)
define MY_HONG(a,b) ab 不能保证完整性
define MY_HONG(a,b) ((a)(b)) 保证完整性
宏展开:本质 就是替换 不做语法检查
调用多少次就会展开多少次 执行过程没有函数调用过程,也不需要函数出入栈,所以带参数的宏 浪费空间 节省时间
代参的函数:代码只有一份,存在代码段,调用的时候去代码段读取函数指令,调用的时候要压栈(保持函数调用钱的相关信息),调用完出栈(恢复调用函数前的相关的信息),所以函数就是浪费了时间,节省了时间
终止宏的作用范围: #undef 宏名
在宏定义中,可以引用已定义的宏名
条件编译:一般情况下,源程序中所有的行都参加编译,但有事希望对部分源程序行只在满足一定条件时才编译,即对这部分源程序行指定编译条件.
1.测试存在
#ifdef *** //无
语句1;//语句1
#else //有
语句2;// 语句2
#endif
2.测试不存在
#ifndef ***
语句1;
#else
语句2;
#endif
3.判断条件是否成立
#if 表达式
语句1
#else
语句2
#endif
方式一:#pragma once 编译器决定 有的不能写,在所有头文件第一行都写一个 会自动过滤重复
#pragma once 放在头文件最前方 强调的是文件名
方式二:c/c++的标准指定
#ifndef 宏 A_H 一般和头文件名一致 名字大写 点用下划线代替 两边都是下划线
#define 宏 A_H
头文件具体的内容
#endif ==》这时 在main.c 写 include
#ifdef 强调的是内容 而不是文件
正数 | 负数 | 备注 | |||
---|---|---|---|---|---|
概念 | 10 | 概念 | -10 | 以1字节为例 | |
原码 | 数据的二进制形式 | 0000 1010 | 原码数据的二进制形式 | 10001010 | 1000 1010 |
反码 | 就是原码 | 0000 1010 | 反码 源码的符号位不变,其他位取反 | 1111 0101 | |
补码 | 就是补码 | 0000 1010 | 补码 反码+1 | 1111 0110 |
注意:无符号数,正数,他们的原码== 反码 ==补码
而负数:
反码=原码的符号位不变其他位取反
补码=反码+1
重要! 负数(任何数据,正数和无符号数只是原补相同)在计算机中存储的都是补码
计算机为什么要补码?
将减法运算变加法运算 不会出错
计算机为了扩展数据的表示范围 -0看作-128所以 一个字节有符号数范围是-128-127
无符号数0-255
有符号数和无符号数 宽度不变 只是范围变了
总结:补码的意义
1.统一了0的编码 +0 -0的补码都是0000 0000
2.将减法运算 变加法运算
存储:
有符号数
负数 以补码 存储(正数其实也是 只是补码原码相同)
十六进制 正数 以原码存储
八进制(每三位二进制 代表一位八进制):以原码存储
越界的情况:以原码存储 //不建议存储值越界
无符号数 在赋值动作之前 完成存储动作 不知道它自己有没有符号unsigned char data= -10 ==》0000 1010
读取: 无符号取 %x %u %o %lu 将内存的原样数据输出
一般选择%x 十六进制 方便 每四位二进制代表一位十六进制
有符号取 %d %hd %ld
首先看内存的最高位
如果最高位为1 内存的数据就是某个数的补码 符号位不变 取反+1 算原码
如果最高位为0 将数据原样输出
值传递与地址传递
值传递:别的类型
地址传递:指针类型和数组类型
想学好指针 必须弄明白内存
内存含义
存储器:计算机的组成中,用来存储程序和数据,辅助CPU进行运算处理的重要部分
外存又叫外部存储器,长期存放数据,掉电不丢失数据。常见的外存设备:硬盘,flash,rom,U盘,光盘,磁带
内存又叫内部存储器,暂时存放数据,掉电数据丢失。常见的内存设备:ram、ddr(与指针有关)
内存是沟通cpu与硬盘的桥梁;暂时存放cpu中的运算数据;暂存与外存交换的数据 物理内存:实实在在的存储设备(一般不许操作)
虚拟内存:操作系统虚拟出来的内存(我们要操作的) 操作系统会在物理内存和虚拟内存之间做映射
在32位系统下,每个进程(运行着的程序)的寻址范围是4G,0x00 00 00 00 ~ 0xff ff ff ff ff (64同理)
在写应用程序的,咱们看到的都是虚拟地址。
在运行程序的时候,操作系统会将虚拟内存进行分区
1.堆 :在动态申请内存的时候,在堆里开辟内存
2.栈:主要存放局部变量(在函数内部,或复合语句内部定义的变量)。
3.静态全局区
( 1):未初始化的静态全局区:静态变量(定义的时候,前面加static修饰),或全局变量,没有初始化的存在此区
( 2):初始化的静态全局区:全局变量,静态变量,赋过初始值的,存在此区
4.代码区:存放咱们的程序代码
5.文字常量区:存放常量的。
内存以字节为单位来存储数据的,咱们可以将程序中的虚拟寻址空间,看作一个很大的一维字符数组
内存地址概述
操作系统给每一个存储单元分配一个编号,从0x00 00 00 00 ~ 0xff ff ff ff
这个编号 咱们称之为地址
指针就是地址
指针变量:是个变量,是个指针变量,即这个变量用来存放一个地址编号(本质就是一个变量,只是变量存放的是内存地址编号(地址/指针))
在32位平台下,地址总线是32位的,所以地址是32位编号,所以指针变量是32位的 即4个字节
在32位平台下,任何类型的地址编号都是4字节
定义指针变量(重要)
定义指针变量的步骤:
1.*修饰指针变量名 *p
2.保持啥类型变量的地址 就用该类型定义一个普通变量 int a
2.从上往下 整体替换
int num = 10;
int *p ; 表示p为指针变量
p= &num;建立 p与num的关系 p保存num的起始位置(首地址)
指针变量的使用//通过p 对所保存的地址空间 进行读写操作
在使用中:p:表示取p所保存的地址编号 对应的空间内容(对p操作==对num操作)指针变量p的解引用
scanf(“%d”,p);==对num赋值(若为&p则表示键盘给p赋值,而不是num)
*p是怎么取出10的 是由p决定的 与num没关系 num只是告诉地址 走多少 走多长 由p决定
#include
void main() {
int num = 8;
int* ptr = #
printf("num的值=%d,num的地址=%p\n",num,&num);
printf("ptr的地址=%p\n", &ptr);
*ptr = 99;
printf("num的值=%d\n", *ptr);
printf("ptr的地值=%p\n", &ptr);
//一级指针
int* ptr1 = ptr;
*ptr1 = 25;
printf("ptr1=%d", *ptr1);
}
指针变量类型
在定义中:
指针变量 自身类型。将指针变量名拖黑 剩下啥类型 指针变量自身就是啥类型
p自身的类型是 int * 。
指针变量 所指向的类型。 将指针变量名和离它最近的一个*一起拖黑 剩下啥类型 指针变量就指向啥类型
p指向的类型为int ==== p保持int类型变量的地址
指向…类型==保存 …类型变量的地址 int ** *p;
指向 int **
多字节数字 要么正存 要么反存 不用纠结 系统自动变正 没影响
指针变量取值宽度 :由指针变量指向的类型长度决定
指针变量的跨度(重要):指针变量指向的类型长度决定
指针类型的强制类型转换//解决需求跨度宽度不等(选择min(跨度/宽度)定义指针变量)
int num = 0x 01020304
char p
p=&num
形式:(short*)(p+1)
指针变量的初始化
如果 局部 指针变量 不初始化 保持的是随机的地址编号(千万不要操作)
int p1=null; //不想让指针指向任何地址 应该初始化为null (千万别操作)((void)0)地址0 不允许操作地址0 保护内存 存放系统根文件
将指针变量初始化为合法的地址(可以取值)
int num =10;
int *p2 = &num;//修饰p2为指针变量,p2=&num; 千万不要看作p2 =&num;
指针变量 p2本质 是一个变量 可以更改指向
p2 = &data; //更改指向
&取地址符 和 指针解引用符(取指针指向的地址的内容,""的作用是引用指针指向的变量值,引用其实就是引用该变量的地址,“解”就是把该地址对应的东西解开,解出来,就像打开一个包裹一样,那就是该变量的值了,所以称为“解引用”。解引用是返回内存地址中保存的值。) 区别(使用中)
num的类型是 int类型
&num的类型是 int 类型
如果对一个变量 取地址,整个表达式的类型就是变量的类型+
如果对 指针变量取* 整个表达式的类型就是 指针变量的类型-*
指针的地址 &p
指针的存放地址 p
指针内容 *p
高级总结:如果&和*同时存在 可以相互抵消。 从右——>左(重要)
指针的注意事项
1.void不能定义变量
void num;//错误 系统不知道num大小
2.void *可以定义变量
void *p;//可以 p的类型是void *,而void 指针类型,32位平台4字节,系统知道给p开辟4字节
//p叫万能指针 p可以保持 任意一级指针
对于p 不能直接使用p操作 必须实现对p的强制类型转换
void test04( )
{
int num = 10;
void *p;
p = #//printf("*p = %d\n",*p);//err 因为p的指向类型为void.系统确定不了宽度
printf("*p = %d\n",*(int *)p);//ok p临时的指向类型为int系统确定宽度4B
}
3. 不要对 没有初始化的 指针变量 取* //因为 p没有初始化 内容随机 也就是p执向了一个未知空间 系统不允许用户 取值*p操作
4.不要对 初始化为null的指针变量取*
5.不要给 指针变量 赋普通的数值 // 不申请 那个地址编号不是合法空间
6.指针变量 不要操作越界的空间
数组元素的指针
需求 定义一个指针变量,保存arr数组 第0个元素的地址 (首元素的地址)
int arr【5】={1,2,3,40}
int *p=null;
p=&arr【0】;
这就是数组元素的指针
p+i:表示第i个元素的地址
*(p+i):表示第i个元素的值
数组的【】和()的关系* 在使用的时候【】就是*()缩写
缩写规则:+左边的值放在【】的左边 +右边的值放放在【】里面
数组名arr 作为类型 代表的是数组的总大小 sizeof(arr)
数组名arr 作为地址 代表的是首元素地址(第0个元素的地址)
为啥arr代表的是第0个元素的地址 (&arr[0])//不是首地址
&arr【0】==&*(arr+0)arr+0arr
总结
1.【】是*()的缩写 //以后可以化简
2. 数组名arr是数组首元素的地址(第0个元素的地址)
arr 与 &arr的区别
arr:数组的首元素地址。+1跳过一个元素
&arr:数组的首地址。+1跳过整个数组
arr和&arr在地址编号一样,但是 类型是完全不一样的
数组名arr是一个符号常量。不能被赋值。(了解)
指向同一数组的两个元素的指针变量间关系
指向同一数组的两个指针,指针变量相减,返回的是相差的元素个数
指向同一数组的两个指针变量 可以比较大小 > < >= <= == !=
指向同一数组的两个指针变量 可以赋值
指向同一数组的两个指针变量 尽量不要相加 。//越界
【】里面在不越界的情况下 可以是负数
指针数组:
本质是数组,只是数组的每个元素是指针
数组指针:
本质是一个指针,保存数组的首地址
定义一个指针变量 保存数组的首地址。+1 跳过整个arr。(复习:调多少根据指向决定)
int arr[5]={10,20,30,40}
int (*p)【5】;
p=&arr;
(p+3)==(arr+3);
对数组指针取得到数组元素地址
&arr带代表数组的首地址
✳(✳(p+0)+3)==*(p【0】+3)==p【0】【3】// *打不了 用✳代替
(指向同一数组的指针变量相减返回元素个数)
二维数组名:代表的是二维数组的首行地址,+1跳过一行
对行地址取* 将编程 当前行的第0列的列地址
((arr+1)+2)==*(arr【1】+2)==arr【1】【2】
数组指针 与 二维数组的关系
任何维度的数组 在物理存储上都是一维的
多级指针
一级指针保存0级指针变量(普通变量)的地址
2级指针保存级指针变量(普通变量)的地址
…
n级指针保存n-1级指针变量(普通变量)的地址
指针作为函数的参数
如果想在函数内部修改外部的值 就需要将外部变量的地址 传递给函数(以指针作为函数的参数)
在函数内部 更改p的指向(在函数内部 给p赋值 就必须传递p的地址)
一维数组名 作为 函数的参数
1.如果函数内部想操作外部数组元素 将外部数组的数组名传递函数(重要!!!)
arr作为类型 代表数组总大小
arr作为地址 代表数组首元素地址
2.一维数组作为函数的形参会被优化成 指针变量
二维数组作为函数的参数
1.如果函数内部想操作外部数组元素 将外部数组的数组名传递函数(重要!!!)
2.指针作为函数返回值
1)函数不要返回普通局部局部变量地址
2)函数指针 本质是指针变量 保存的是函数的入口地址
3)对函数指针取*无意义
动态你内存申请相关概念
1.在数组一章中,介绍过,数组的长度是预先定义好的,早整个程序中固定不变;
2.但是在实际的编程中,往往会发生这种情况,即所需的内存空间取决于实际输入的数据,而无法预先确定
3.为了解决上述问题,C语言提供了一些内存管理函数,这些内存管理函数可以按需要动态分配内存空间,也可把不再使用的内存空间回收再次利用
静态分配和动态分配
静态分配
1.在程序编译或运行过程中,按事先规定大小分配内存空间的分配方式
2.必须事先知道所需空间的大小
3.分配在栈区或全局变量区,一般以数组的形式
4.按计划分配
动态分配
1.在程序运行过程中,根据需要大小自由分配所需空间
2.按需分配
3.分配在堆区,一般使用特定的函数进行分配
动态内存申请相关函数
1.malloc 分配内存空间函数
函数原型:void malloc (unsigned int num_bytes);
调用形式:(类型说明符)malloc(size)
功能:在内存的动态存储区(堆区)中分配一块长度为size字节的连续区域用来存放类型说明符指定的类型
函数原型返回void指针,使用时必须做相应的强制类型转换
分配的内存空间内容不确定,一般使用memset初始化//memset(p,0,nsizeof(int))
返回值:分配空间的起始地址(分配成功)
null(分配失败)
注意:
1.在调用malloc之后,一定要判断一下,是否申请内存成功
2.如果多次malloc申请的内存,第一次和第二次申请的内存不一定是连续的
例如 void *p(unsigned int 5);
2.ferr函数(释放内存函数)
头文件:#include《stdlib,h》
函数定义:void free (void *ptr)
函数说明:free函数释放ptr指向的内存
注意ptr指向的内存必须是malloc calloc relloc动态申请的内存
//释放堆区空间 空间使用权限的回收 是否对空间内容清零 不确定
3.calloc函数//自动清零 不需要memset
#include 《stdlib.h》
void *calloc(size_t nmemb,size_t size)
功能:在内存的堆中,申请nmemb块,每块的大小为size个字节的连续区域。总大小 = nmemb * size
参数:size_t 实际是无符号整型,它是头文件中,typedef定义出来的。
返回值:
返回 申请的内存的首地址(申请成功)
返回 NULL(申请失败)
例如:char *p=(char=)calloc(3,100);
4.realloc 动态追加或减少空间
#include《stdlib.h》
void* realloc(void *s,unsigned int newsize)
功能:在原先s指向的内存基础上重新申请内存,新的内存大小为new_size个字节,
如果原先内存后面有足够大的空间,就追加,
如果后边的内存不够用,则relloc函数会在堆区找一个newsize’个字节大小的内存申请,将原先内存中的内容拷贝过来,然后释放原先的内存 最后返回新内存的地址
参数: s 原先开辟内存的首地址
newsize:新申请的空间大小
返回值:新申请的内存的首地址
5.堆区空间使用注意事项
1.指向堆区空间的指针变量 不要随意的更改该指向
2.不要操作已经释放的空间
3.不要堆堆区空间重复释放 //解决方案 if(p!=NULL){free(p);p=NULL;}
PS:不使用记得free防止内存泄漏。
只要是以str开头的函数 都是遇到’\0’结束
#include
1.strlen测量字符串长度
原型:int strlen (const char *str)
功能:计算字符串长度,不含’\0’
参数:存放字符串的内存空间 首地址
说明:形式参数str用const修饰,表示str指向的空间中的数据只读不可修改
*size_t strlen (const char s)
s:被测量的字符串首元素地址
返回值:str的长度 不包含’\0’
不会通过s修改s指向的空间内容
2.字符串拷贝函数(重要)
strcpy
原型:char *strcpy(char *dest,const char *src)
功能:把src所指向的字符串赋值到desr所指向的空间中
返回值:返回dest字符串的首地址
注意:’\0’也会拷贝过去
strncpy
原型:char *strncpy(char *dest,const char *src ,int num)
功能:把src指向字符串的前num个复制到dest所指向的空间中
返回值:返回dest字符串的首地址
注意:’\0’不拷贝
3.字符串的拼接
stract
原型:char *strcat(char *dest,const char *src)
功能:将src的字符串拼接到dst的末尾(dst第一个’\0’的位置)
strncat
原型:char *strncat(char *str1,char *str2,int num)
功能:将str2前num个自贸链接到str1后面
返回值:返回str1的字符串首地址
注意:‘\0’会一起拷贝过去
4.字符串比较函数(重要)
strcmp整个字符串比较
原型:int strcmp (char *str1,char *str2)
功能:比较str1和str2的大小;逐个进行比较。只有相等才比较下一个
返回值:相等返回0;
str1大于str2返回》0
str1小于str2返回《0
str1=str2 返回=0
strncmp字符串局部比较
字符串变换函数
strchr 字符匹配函数
原型:char* strchr(const char *str1,char ch)
功能:在str1中找ch出现位置
返回值: 返回第一个位置
如果找不到返回空
strstr字符串匹配函数
原型:char *strstr (const char *s1,const char *s2)
功能:从s1中找s2
返回值:找到返回第一次的位置
找不到返回空
字符串处理函数
memset
原型:void* memset(void *str,char c ,int n)
功能:将str所指向的内存区的前n个全部用c填充
常用清除指定空间,比如数组或malloc的空间
返回值:返回str的地址
atoi/stol/atof 将字符串转换为数值
int atoi(const char *str)
long atol (const char *str)
double atof (const char *str)
功能:将str所指向的数字字符串转化为int\long\double
strtok字符串切割函数 //会对原串更改,调用一次切一次
*char *strtok(char s[],const char delim)
功能:字面意思
s 指向欲分割的字符串
delim则为分割字符串中包含的所以字符
当strtok()在参数s的字符串中发现参数delim中包含的分割字符时,则会将该字符改为\0字符,当连续出现多个时只Ithaca第一个为\0
(保证上一次切割成功,采用进行下一次的必要)
第一次调用时:strtok()必须给与参数s字符串。往后的调用则将参数s设置成null,没错调用成功则返回指向被分割出片段的指针
返回值:切割成功 返回切割到字符换片段的首元素地址 失败返回null
sprintf组包
printf 输出到 终端
sprintf 输出到 字符串数组
fprintf 输出到 文件
int sprintf(buf,“格式”,数据)
buf:用来存放组好的报文
“格式”:按照格式组包
数据:各个零散的数据逗号隔开,按顺序匹配一一对应
返回值:返回值是组好的报文的实际长度(不包含“\0”)
sscanf解包
sscanf(buf,“%dA%dB%dC”,&A,&B,&C);
%d 只能图区0-9
%c 只能提取一个字符
%f 只能提取浮点数
%s 提取一个字符串 遇到空格、回车、‘\0’的时候停止获取
”%*s“流指针 只取 不要
高级用法
1.使用%*s %*d 跳过提取内容
2.使用%[n]s %[n]d提取指定宽度的字符串或数字
结构体的定义
1.结构体类型名:
指定一个结构体类型,它相当于一个模型,但其中并无具体数据,系统对之也不分配实际内存单元
2.结构体变量名
实际分配空间一为了能在程序中使用结构类型的数据,应当定义结构体类型的变量,并在其中存放具体的数据
1.先定义类型 在定义变量(推荐)
struct stu //struct 是结构体关键字 stu是结构体类型名
{ //使用结构体类型 必须是 struct stu
int num; //num name age叫做结构体中的成员
char name【32】;//定义结构体类型的时候 不要给成员赋值(重要)
int age;
};
struct stu lucy;//不定义lucy时 不开辟空间
2.定义类型的同时定义变量
struct stu
{
int num;
char name【32】;
int age;
}lucy;
定义一次性结构体
struct
{
int num;
char name【32】;
int age;
}lucy;
测量结构体类型sizeof(struct stu);类型不占空间 调用才开辟
输出 调用lucy.num 访问结构体中的成员(一定要遵循成员自身的类型) num ==》int
printf(“num=%d\n”,lucy.num);
printf(“name=%s\n”,lucy.name);
注意事项:
1.结构体变量的成员引用必须单独使用:
结构体变量名.成员名
2.允许具有相同类型的结构体变量可以相互赋值
多用于结构体成员整体操作(排序等)
3.结构体可以定义的时候进行初始化
4.允许嵌套定义结构体变量,成员引用多级引用
结构体变量获取键盘输入
声明://略
scanf(”%d %s %d“,&lucy.num,&lucy.name,&lucy.age);
结构体变量赋值
方式一:逐个成员赋值 bob2.num=bob1.num; strcpy(bob2.name,bob1.name);
方式二:相同类型的结构体变量可以直接复制 bob2=bob1;
方法三:方法二的底层实现
memcpy(&bob,&lucy,sizeof(struct stu));
结构体数组//数组每个元素是结构体
struct stu arr[5];
取arr[2].name
struct stu arr[5]={{100,‘100’,100},{100,‘100’,100},{100,‘100’,100}}
冒泡排序
arr 3 2 1 5 4
第0轮:j=0;j+1
第i轮:j=0;j
{
int tmp=0;
tmp =arr[j];
arr[j]=arr[j+1];
arr[j+1]=tmp;
}
for(i=0;i
for(j=0;j
int tmp=0;
tmp =arr[j];
arr[j]=arr[j+1];
arr[j+1]=tmp;
flag=1;
}}
if(flag=0){ //已经有序
break;
}
}
结构体数组排序
for(i=0;i
for(j=0;j
struct stu tmp;//按照num的大小排序
tmp =arr[j];
arr[j]=arr[j+1];
arr[j+1]=tmp;
flag=1;
}}
if(flag=0){ //已经有序
break;
}}
typedef使用步骤//给已有类型取别名
1.先用已有的类型定义一个实体
2.用别名替换变量名
3.在整个表达式前 添加typedef
注意:不能创造新的类型
案例:给函数指针取别名
int my_ add(int x,int y)
{
return x+y;
}
//FUN P是一个丽数指针类型 该函数必须有两个int形参以及一个int返回值
typedef int (*FUN_ P)(int x,int y);
void test04( )
{
FUN_ P p = my_ _add;
printf("%d\n",p(100, 200));/ /300
}
结构体指针 即结构体的地址
&lucy :结构体的首地址
STU *p=&lucy;
*p==lucy;
调用格式:
1.(*p).name (*p).age
2.p->name p->age
如果.或->左边是普通结构体变量 就用.
如果.或->左边是地址 用->
3.lucy.name
结构体指针变量不能直接操作成员,必须保存一个地址。
#include
#include
struct stu{
int id;
char name[32];
char sex;
int age;
};
void main(int argc,char *argv){
struct stu *s;//定义一个结构体指针变量
//在堆区开辟结构体空间并将其地址保存在结构体指针变量中
s = (struct stu *)malloc(sizeof(struct stu));
s->id=1001;
strcpy(s->name,"张三");
s->sex='Boy';
s->age=20;
printf("%d - %s - %c - %d \n",s-id,s->name,s->sex,s->age);
}
结构体内存分配
结构体变量大小是所有成员之和?
但是在实际给结构体分配内存的时候,是有规则的。
规则1:以多少个字节为单位开辟内存
给结构体变量分配内存的时候,会去结构体变量中找基本类型的成员
哪个基本类型的成员占字节数多,就以它大大小为单位开辟内存,
在gcc中出现了double类型的例外
(1): 成员中只有char型数据,以1字节为单位开辟内存。
(2):成员中出现了short类型数据,没有更大字节数的基本类型数据。
以2字节为单位开辟内存
(3):出现了int float没有更大字节的基本类型数据的时候以4字节为单位开辟内存。
(4):出现了double类型的数据
情况1:
在vc里,以8字节为单位开辟内存。
情况2:
在gcc里,以4字节为单位开辟内存。
无论是那种环境,double 型变量,占8字节。
注意:
如果在结构体中出现了数组,数组可以看成多个变量的集合。
如果出现指针的话,没有占字节数更大的类型的,以4字节为单位开辟内存。
在内存中存储结构体成员的时候,按定义的结构体成员的顺序存储。
例如:
struct stu{
char sex;
int age;
}lucy; lucy的大小是4的倍数
规则2:字节对齐
(1): char1字节对齐,即存放char型的变量,内存单元的编号是1的倍数即可。
(2): short int2字节对齐,即存放short int型的变量,起始内存单元的编号是2的倍数即可。
(3): int4 字节对齐,即存放int型的变量,起始内存单元的编号是4的倍数即可
(4): longint 在32位平台下,4字节对齐,即存放long int型的变量,起始内存单元的编号是4的倍数即可
(5): float 4字节对齐,即存放float型的变量,起始内存单元的编号是4的倍数即可
(6) : double
a.vc环境下 8字节对齐,即存放double型变量的起始地址,必须是8的倍数, double变量占8字节
b.gcc环境下 4字节对齐,即存放double型变量的起始地址,必须是4的倍数, double变量占8字节。
注意3:当结构体成员中出现数组的时候,可以看成多个变量。
注意4:开辟内存的时候,从上向下依次按成员在结构体中的位置顺序开辟空间
指定对齐原则:
使用#pragma pack改变默认对其原则
格式:#pragma pack (value)时的指定对齐值value。
注意:
1.value只能是: 124 8等
2.指定对齐值与数据类型对齐值相比取较小值
结构体指针作为函数的参数
结构体内存对齐:
对齐规则:
1.确定分配单位:每一行应该分配的字节数,有结构体最大的基本类型长度确定
2.确定成员的起始位置的偏移量=成员基本类型的整数倍
3.收尾工作:结构体总大小==分配单位的整数倍
结构体嵌套结构体:结构体2做结构体1的成员
访问:data1.data2.name(延续上面定义类型)
//struct data1={10,20,30,40}
struct data1={10,20,{30,40}}// 推荐
结构体嵌套结构体的内存对齐
1.确定分配单位:每一行应该分配的字节数
所有结构体中最大的基本类型长度决定
2.确定成员的便宜量=自身类型的整数倍
如果是结构体成员,偏移量=被嵌套结构体中最大基本类型的整数倍
结构体成员中的成员偏移量 相对于被嵌套的结构体
3.收尾工作 :结构体的中大小 = = 分配单位的整数倍
结构体成员的总大小 = = 被嵌套的结构体里面最大借本类型整数倍
强制类型对齐
指定对齐规则
1.使用#pragma pack改变默认对齐规则
格式:
#pragma pack(value)时的指定对齐值value
注意:
1.value只能是:1 2 4 8等
2.指定对齐值与数据类型对齐值相比取较小值
步骤:
1.确定分配单位:每一行应该分配的字节数,min(value,默认分配单位)
2.成员偏移量=成员自身类型的整数倍
3.收尾工作=分配单位的整数倍
//结构体的成员顺序也能影响占结构大小
1.信息在计算机的存取长度一般以字节为单位
2.有时存储一个信息不必用一个或多个字节
C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,以位为单位的成员称为“位段”或称“位域”
struct packed_ data {
unsigned int a:2;
unsigned int b:6;
unsigned int c:4;
unsigned int d:4;
unsigned int i;
} data;
其中a, b, c, d分别占2位,6位,4位,4位,;i为整型,占4个字节
位段一般只考虑unsigned int类型 也可以考虑unsigned char
相邻位域可以压缩(压缩的位数 不能超过 成员自身大小)
位段的使用:
位段 不能取地址 (不够一个字节,地址以字节为单位)
位段的赋值 不要超过位段的大小
char a:2
char :2//无意义位段(占有两位) 占位 把a b隔开
char b:2
位段注意:
1、对于位段成员的引用如下: data.a =2 赋值时,不要超出位段定义的范围;如段成员a定,义为2位,最大值为3,即(11) 2 所以data.a=5,就会取5的低两位进行赋值101
2、位段成员的类型必须指定为整形或字符型
3、一个位段必须存放在一一个存储单元中,不能跨两个单元第一个单元空间不能容纳下一个位段,则该空间不用, 而从下一个单元起存放该位段
位段的存储单元:
(1): char型位段存储单元是1个字节
(2): short int 型的位段存储单元是2个字节
(3): int的位段,存储单元是4字节
(4): long int的位段,存储单元是4字节
位段的长度不能大于存储单元的长度
(1): char型位段不能大于8位
(2): short int型位段不能大于16 位
(3): int的位段,位段不能大于32位
(4): long int的位段,位段不能大于32位
可以定义无意义位段,如:
unsigneda: 1;
unsigned : 2;
unsigned b: 3;
1.在进行某些算法的时候,需要使用几种不同类型的变量存到同一段内存单元中,几个变量所使用内存空间相互叠加
2.这种几个不同的变量共同占用一段内存的结构在C语言中,被称作“共用体”类型结构
3.共用体所有成员占有同一段地址空间
结构体与共用体的区别
结构体:struct 所有成员拥有独立的空间
struct stu{
char a;
short b;
int c;
};//a b c成员拥有独立的空间
共用体(联合体):union
所有的成员 共享同一份空间
union stu{
char a;
short b;
int c;
};// a b c成员共享同一份空间
共用体的空间大小 由最大的成员决定
共用体的特点:
1、同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用
2、共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖
3、共用体变量的地址和它的各成员的地址都是同一地址
4、共用体变量的初始化 union data a={123};初始化共用体为第-一个成员
共用体:取最后一次赋值的值,变量名指向是相同的
共用体虽然共有同一份空间,但是从空间读取的字节数 是有成员自身类型决定
将变量的值一一列举出来,变量的值只限于列举出来的值的范围内
枚举类型定义
enmu 枚举名
{
枚举值表//符号常量:hongtao,heitao=30,meihua=40,fangkuai
};
枚举变量的定义方法
emnu 枚举类型名 枚举变量名;
在枚举值表中应列出所有可用值,也称枚举元素
在枚举变量仅能取枚举值所列元素
// enmu 枚举名 HONGTAO = hongtao;
枚举列表的值,默认从0开始 没有赋值 默认递增
1.枚举值是常量,不能在程序中用赋值语句再对它赋值
2.枚举元素本身由系统定义了一个表示序号的数值 默认0,1,2,3,4 etc.
3.可以改变枚举值的默认值
enmu week{
mon,tue=2,wed,thu=8,fri,set,sun
};
enmu week day = mon;
printf(“%d”,day);//0
enmu week day =tue;
printf("%d",day);//2
enmu week day = fri;
printf("%d",day);//9
数组:便于遍历
静态数组:不能合理利用空间
动态数组:不便于插入或删除数据(会涉及到大量的数据移动)
基本概念:链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中指针链接次序,实现的一种线性存储结构
链表由一系列节点组成,节点在运行时,动态生成(malloc),每个节点包括两个部分
1.存储数据的数据域
2.存储下一个节点空间的指针域
链表的组成,一般采用结构体类型的形式
代码实现
链表节点的定义:
typedef struct stu{
//数据域(自定义)
int num;
char name【32】;
float score;
//指针域(保存下一个节点地址 struct stu*)
struct stu *next;//保存下一个节点地址
}STU;
静态链表//不重要 链表都是用动态的
void test(){
STU *head =NULL;
STU *PB=head;//不能再下方定义
STU data1={};
STU data2={};
STU data3={};
STU data4={};
STU data5={****};
head = &data1;
data1.next = &data2;
data2.next = &data3;
data3.next = &data4;
data4.next = &data5;
data5.next = NULL;
//遍历链表
while(pb != NULL)
{
printf("%d %s %f \n",pb->num,pb->name,pb->score);
pb=pb->next;//pb指向下一个节点
}}
动态链表操作//分文件分函数的方法实现
1.布局整个程序框架
main.c
头文件:定义,声明都在头文件
防止头文件重复包含 #pragma once
#ifndef LINK_H //linux用的多
#define LINK_H
//链表节点类型定义
typedef struct student{
//数据域
int num;//学号
int score;//分数
char name[20];//姓名
//指针域
struct student *next
}STU;
#endif
//链表的创建
#include
#include
//链表节点类型定义
typedef struct student{
//数据域
int num;//学号
int score;//分数
char name[20];//姓名
//指针域
struct student *next
}STU;
void link_creat_head(STU **p_head,STU *p_new){
STU *p_mov = *p_head;
if(*p_head == NULL)//当第一次加入链表为空时,head执行p_new
{
*p_head = p_new;
p_new->next=NULL;
}else//第二次以后加入链表
{
while(p_mov->next != NULL){
p_mov = p_mov -> next;//找到原有链表的最后一个节点
};
p_mov->next=p_new;//将新申请的节点加入链表
p_new->next=NULL;
};
};
int main(){
STU *head = NULL,*p_new=NULL;
int num,i;
printf("请输入链表初始个数:\n");
scanf("%d",&num);
for(i=0;i<num;i++){
p_new = (STU*)malloc(sizeof(STU));//申请一个新节点
printf("请输入学号、分数、名字:\n");//给新节点赋值
scanf("%d %d %s",&p_new->num,&p_new->score,&p_new->name);
link_creat_head(&head,p_new);//将新节点加入链接
};
};
2.链表遍历
void link_print(STU *head){
STU *p_mov;//定义新的指针保存链表的首地址,防止使用head改变原本链表
p_mov = head;
//当指针保存最后一个节点为NULL时,循环结束
while(p_mov=head){
//先打印当前指针保存节点的指针域
printf("num=%d score=%d name:%s\n",p_mov->num,p_mov->score,p_mov->name);
//指针后移,保存下一个节点的地址
p_mov = p_mov->next;
};
};
3.链表插入节点
1)头部之前插入
2)尾部插入
3)链表有序插入//以某个参数为参考
void link. _insert_ num(STU **p_ .head,STU *p_ _new)
{
STU *pb,*pf;
pb=pf=*p_ .head;
if(*p_ _head ==NULL)// 链表为空链表
{
*p_ _head = p_ .new;
p_ new->next=NULL;
return;
}
while((p_ new->num >= pb->num) && (pb->next !=NULL) )
{
pf=pb;
pb=pb->next;
if(p_ new->num < pb->num) //找到一个节点的num比新来的节点num大,插在pb后面
{
if(pb== *p_ head)//找到的节点是头节点,插在最前面
{
p_new->next=*p_head;
*p_head=p_new;
}
else
{
pf->next=p_ new;
p_ new->next = pb;
}
}
else//没有找到pb的num比p_ _new->num大的节点,插在最后
{
pb->next =p_ new;
p_ new->next =NULL;
}
}
4.链表节点的查找 遍历匹配查找
STU * link_search_num(STU *head,int num){
STU *p_mov;
//定义的指针变量保存第一个节点的地址
p_mov=head;
//当没有到达最后一个节点的指针域时,循环继续
while(p_mov != NULL){
if(p_mov->num==num)//找到了
{
return p_mov;
}
p_mov=p_mov->next;
};
return NULL;//没有找到
}
5.删除链表节点
//链表结点的删除
void link_ delete_ num(STU **p_head,int num)
{
STU *pb, *pf;
pb=pf=*p_ head;
if(*p_ head == NULL)//链表为空,不用删
{
printf("链表为空,没有您要删的节点");\
return ;
}
while(pb->num != num && pb->next !=NULL)//循环找, 要删除的节点
{
pf=pb;
pb=pb->next;
}
if(pb->num == num) //找到了-一个节点的num和num相同
{
if(pb == *p_head)//要删除的节点是头节点
{
//让保存头节点的指针保存后一个节点的地址
*p_head = pb->next;
}
else{
//前一个节点的指针域保存要删除的后一个节点的地址
pf->next = pb->next;
}
free(pb) ;
pb=NULL;
}
else//没找到
{
printf("没有您要删除的节点\n");
}
6.释放链表节点 — 必须逐个节点释放
void link_free(STU **p_head){
//定义一个指针变量保存头节点的地址
STU *pb=*p_head;
while(*p_head!=NULL){
//先保存p_head指向的节点的地址
pb=*p_head;
//p_head保存下一个节点的地址
*P_head=(*p_head)->next;
//释放节点并防止野指针
free(pb);
pb=NULL;
};
};
7.链表逆序
8.链表的排序
选择法排序
前提条件:指针变量作为结构体的成员
结构体浅拷贝:两个结构体变量中的指针成员指向同一空间
结构体深拷贝:两个结构体变量中的指针成员指向独立空间
文件的基本概念
磁盘文件,设备文件(经过驱动优化后类似指针)
1.文件的存取过程
内存–>文件缓冲区–>磁盘
文件缓冲区的目的:提高存取效率 和磁盘寿命
2.磁盘文件分类
物理上 所有磁盘文件都是二进制 物理上是顺序存储
逻辑上(用户角度):文本文件 基于字符编码
二进制文件 基于值编码的文件
区别: 1.译码
2.空间利用率
3.可读性
文件的打开与关闭
C语言不能直接操作文件,只能采用库函数间接对文件进行操作
打开文件会得到一个文件指针fp
FILE *指针变量标识符 // FILE *fp=NULL
FILE *fp = NULL;
fp =fopen(文件名,文件使用方式);
fclose(文件指针)关闭文件
文件使用方式 :第一个参数 r w a + / 第二个参数 b t
r只读
w只写
a追加
+同时以读写打开
b二进制
t文本
字节读写函数:fgetc和fputc
ch=fgetc(fp);//读取一个字节 末尾 文本EFO;feof二进制文件
fputc(buf【i】,fp);逐个写入
字符串读写函数: fgets和fputs
fgets(str,n,fp);遇到换行或EOF提取结束并读取换行符 在最后加一个\0
失败返回NULL 成功返回首元素地址
数据块读写函数: fread和fwrite
fwrite不方便查看 但不影响程序读
格式化读写函数: fscanf和fprintf
文件随机读写
不用函数 关闭再打开 把首位置给指针
rewind 复位文件流指针
ftell 取得文件流目前的读写位置 返回字节数
fseek函数 移动文件流的读写位置(多用于二进制文件)
fseek(fp,20,0/1/2)开头/当前/末尾
文件结束检测函数(二进制也可用,避免二进制无法用EOF的尴尬)
feof(文件指针)返回值未结束返回0 结束非0
读写文件出错检测函数
ferror(文件指针);返回值0未出错 否则有错
文件出错标志和文件结束标准置0函数
ckearerr(文件指针);
对称加密体制:同一种key加密解密
文件加密器
1.原理分析
加密过程:
解密过程:
详细设计api参考//别人给你api,但没完善
//没有声明标准库
#include"fun.h"
int main(){
while(1){
int cmd=0;
print_help();
scanf("%d",&cmd);
if(cmd=1){
char src_file[31]="";
char dest_file[31]="";
unsigned long file_length=0;
char *file_data=NULL;
char *data=NULL
unsigned int passward = 0;
//1.获取源文件目的文件名
//void get_file_name(char *dest_file_name,char *src_file_name)
get_file_name(dest_file[31],src_file[31]);
//2.获取源文件名 对应的文件 内容
//char * read_src_file(unsigned long *file_length,char *src_file_name)
file_data = read_src_file(&file_length,src_file[31]);
//3.获取用户输入密码
printf("请输入密码");
scanf("%u",&passward);
//4.对文件内容加密
//char *file_text_encrypt(char *src_file_text,unsigned long int length,unsigned int passward)
file_data=file_text_encrypt(file_data,file_length,passward );
//5.对加密好的文件内容 保存到目的文件名中
//void save_file(char* text,unsigned long int length,char *file_name)
save_file(file_data,file_length,dest_file);
}else if(cmd=2){
//1.获取源文件目的文件名
//void get_file_name(char *dest_file_name,char *src_file_name)
get_file_name(dest_file[31],src_file[31]);
//2.获取源文件名 对应的文件 内容
//char * read_src_file(unsigned long int *file_length,char *src_file_name)
file_data = read_src_file(&file_length,src_file[31]);
//3.获取用户输入密码
printf("请输入密码");
scanf("%u",&passward);
//4.对文件内容解密
//char *file_text_decrypt(char *src_file_text,unsigned long int length,unsigned int passward)
file_text_decrypt(file_data,file_length,passward);
//5.对加密好的文件内容 保存到目的文件名中
//void save_file(char* text,unsigned long int length,char *file_name)
save_file(file_data,file_length,dest_file);
}else if(cmd=3){
}else{
printf("请输入合法选项")
}
};
return 0;
}
//fun.h 代码库
void print_help(void){
printf("*****1.加密文件******\n")
printf("*****2.解密文件******\n")
printf("*****3.退出程序******\n")
}
void get_file_name(char *dest_file_name,char *src_file_name){
printf("please input your yuanwenjian 30char")
scanf("%s",src_file_name);
printf("please input your mudiwenjian 30char")
scanf("%s",dest_file_name);
return;
}
char * read_src_file(unsigned long *file_length,char *src_file_name){
FILE *fp =NULL;
fp=fopen(src_file_name,"r");
if(fp==NULL){
perror("fopen");
return NULL;
}
//获取文件长度
fseek(fp,0,2);
*file_length=ftell(fp);
rewind(fp);
//根据文件长度申请堆区空间
data=(char * )calloc(1,*file_length);
if(data==NULL){
perror("calloc");
return NULL;
}
//一次性读取文件内容
fread(data,(1,*file_length,1,fp);
//将空间返回
return data;
}
char *file_text_encrypt(char *src_file_text,unsigned long int length,unsigned int passward){
int i=0;
for(i=0;i<length;i++){
src_file_text[i] += passward;
}
return src_file_text;
}
void save_file(char* text,unsigned long int length,char *file_name){
FILE *FP =NULL;
fp = fopen(file_name,"w");
if(fp==NULL){
perror("fopen");
return;
}
fwrite(text,length,1,fp);
fclose(fp);
if(text!=NULL){
free(text);
text=NULL;
}
printf("保存成功!\n");
}
char *file_text_decrypt(char *src_file_text,unsigned long int length,unsigned int passward){
int i=0;
for(i=0;i<length;i++){
src_file_text[i] -= passward;
}
return src_file_text;
}
//fun.c 声明c文件
#ifndef __FUN_H__
#define __FUN_H__
extern void print_help(void);
extern void get_file_name(char *dest_file_name,char *src_file_name);
extern char * read_src_file(unsigned long *file_length,char *src_file_name);
extern char *file_text_encrypt(char *src_file_text,unsigned long int length,unsigned int passward)
extern void save_file(char* text,unsigned long int length,char *file_name)
extern char *file_text_decrypt(char *src_file_text,unsigned long int length,unsigned int passward)
#endif
后续更新关于指针和结构体的深入学习