导图社区 C语言
C语言0基础进阶思维导图笔记,数据记录编译器为:VS2019及2022版本建议配合B站UP主鹏哥C语言的C语言教学视频学习。
编辑于2022-03-20 20:11:55C语言
数据记录编译器为:VS2019及2022版本 建议配合B站UP主鹏哥C语言的C语言教学视频学习
C 语言 int main() 和 int main(void) 的区别: int main(void) 指的是此函数的参数为空,不能传入参数,如果你传入参数,就会出错。 int main() 表示可以传入参数。
.c(源文件) >> .obj(目标文件) >> .exe(可执行程序) .h是C语言中的头文件后缀
函数结构分为:函数头(首);函数体 C语言--非零为真,0为假!
函数首:
int main( )
函数体:
{ int a; a = 1; printf(……); return 0; }
大写的NULL代表--空指针 小写的Null/NUL代表--'\0'
文档!
MSDN精简版下载 大小:75Mb
https://www.aliyundrive.com/s/FVSqxcvfHMP
PDF书籍下载
https://www.aliyundrive.com/s/EB4Kq4fhkHN
内含: C Primer Plus 第6版 中文版 高质量的c-c++编程指南 谭浩强c语言 剑指Offer C陷阱与缺陷 C和指针
PDF查看工具官网:https://www.sumatrapdfreader.org/download-free-pdf-viewer 大小参考版本:3.3.3 = 17Mb
C语言运算符优先级 (原图)
原图/Excel源文件:https://www.aliyundrive.com/s/M9g1ztiSCpz
图片查看软件推荐 (别用什么看图王、wps了)
这是它支持的格式
官网:https://picview.org/download
大小参考版本: 1.6.6 = 73Mb
如何成为学习高手
https://www.aliyundrive.com/s/qurc4iFHyso
*<C基础>*
常用函数
常见关键字
auto//自动
局部变量都会有一个auto,既然都有还不如省略了
assert//断言
需要引用<assert.h>头文件
作用:结果为真什么都不发生,结果为假程序报错
break//中止
case//入口,配合switch语句使用
default//当switch语句中所有case都不成立时执行,类似if语句的else
char//字符
const//常量
变量前加const,变量变常变量,标准定义:不可修改
const 有什么用途?(请至少说明两种)
(1)可以定义 const 常量
(2)const 可以修饰函数的参数、返回值,甚至函数的定义体。被 const 修饰的东 西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
continue//跳过本次循环
do
double//双精度浮点/实数
else//if语句多分支
enum
extern
声明来自外部文件的函数
float//单精度浮点/实数
在使用 %.2f//显示小数点后两位 时,有四舍五入
for//循环
go to//语句
无条件跳转语句
if//语句
int//整形
long//长整形
register//定义寄存器变量(建议作用)
return//返回
short//短整形
signed//有符号数
unsigned//无符号数
sizeof//查看类型占用的字节
static
修饰局部变量: 局部变量生命周期变长
修饰全局变量: 改变了变量的作用域>让静态的全局变量只能在自己所在的源文件内部使用
修饰函数: 改变函数的链接属性
struct//结构体关键字
switch//分支语句
typedef//类型定义or类型重定义
图示
union
void//空的
volatile
while//循环语句
memset//内存操作函数
代码格式:memset(arr,int value,num);
_Bool 类型表示布尔值(true 或 false)
_Complex//复数
_Imaginart//虚数
等...
C文件结构
#include//引入头文件
常用头文件
<stdio.h>//标准输入输出
scanf//输入
printf//输出
%d / %i //打印整型十进制数据
%ld//打印(long)长整形数据
%lld//打印(long long)长长整形数据
%u//打印无符号十进制整数
%o//打印八进制数据
0 num
%x//打印十六进制数据
0x num
%f//打印浮点型(fload)数据
%lf//打印双精度浮点型(double)数据
%c//打印(char)字符格式的数据
%s//打印字符串数据
%p//以地址的形式打印
%e//打印用科学表示格式的浮点数
%g//使用%f和%e表示中的总的位数表示最短的来表示浮点数;%G 同 %g格式,但表示为指数
extern//声明外部定义变量
<math.h>//数学运算
sqrt//计算平方根值
sqrt(平方式)
pow//计算次方值
pow(底数,指数)
<string.h>//操作c字符串和数组
strlen//计算字符串长度
strcpy//字符串拷贝
strcmp//字符串比较
strcmp(str1,str2);//如果str1<str2,则返回一个<0的数(-1),如果等于则返回0,如果str1>str2,则返回一个>0的数(1)
<windows.h>
Sleep()//控制时间,毫秒为单位
<stdlib.h>
system()//执行系统命令函数
qsort(目标数组起始位置,数组元素的个数,1个元素占用字节的大小,自定义cmp比较函数);//排序函数 cmp自定义函数代码格式:int cmp(const void* e1, const void* e2) { return 比较函数体; }
rand()//生成随机数函数
rand是一个无参函数
srand()//配合rand函数使用
由于rand生成的随机数是固定的,它不会随着程序的每次运行而重新生成随机数。 所以需要srand函数调和
time函数需要头文件<time.h>
<assert.h>
<limits.h>
可查看整形家族的取值范围
<float.h>
可查看浮点型家族的取值范围
等等,很多很多
#define//定义常量和宏
#denfine//定义标识符常量
#denfine//也可以定义宏-带参数
图示
数据类型
字符串
char arr[ ] = "abc";可打印字符串
由双引号""引起来的一串字符称为(字符串字面值),简称<字符串>
转义字符\0--代表字符串结束标志;在计算字符串长度时,不算字符串的内容
int//整型--占用2或4个字节,正常是4字节,老年的编译器可能是2字节
范围:-32768~32767
short//短整型--占用2个字节
long//长整型--占用4或8个字节
long long//更长的整型--占用8个字节
unsigned int//无符号整型(非负)
范围:0~65535
char//字符型--占用1个字节
有符号char的范围:-128~127 无符号char的范围:0~255
**由于char存入的字符是以ASCII码存放的,字符型存入内存中也是整型 (初学可直接忽略)
flort//单精度浮点数(实型)--占用4个字节
范围:
1E10 <-> E前E后必有数,E后必须是整数
double//双精度浮点数(实型)--占用8个字节
_Bool//布尔类型
头文件:<stdbool.h>
C语言用值1(非0)表示true(真),0表示false(假);所以_Bool类型实际上也是一种整数类型,原则上它仅占用1位存储空间。
_Bool 等价 bool;由于C语言后期引入bool类型,早期为了不影响老代码的运行,所以加了下划线 _Bool 为的就是不让老代码起冲突。
bool类型可作用于一些判断
其他:
[拓展,不算数据类型] (在键盘上敲Ctrl+Z) > getchar会获取到 <EOF> [全称:end of file 文件结束标志 数值为<-1>]
常量与变量
变量的分类
局部变量
定义在代码块{ }内部,为局部变量
局部变量只在代码块{ }内有效,{ }外调用会报错
局部变量不初始化,默认随机数
全局变量
定义在代码块{ }外部,为全局变量
局部变量与全局变量相同时,局部变量优先全局变量.
全局变量不初始化,默认为0
变量的作用域与周期
作用域:哪里能用,哪里就是变量的作用域; 局部变量的作用域是代码块{ }内; 全局变量的作用域是整个工程
周期:局部变量的周期为进入作用域生命周期开始,出作用域生命周期结束;全局变量的周期为整个程序的生命周期
常量(不可修改)
字面常量
直接写一个数字
const修饰的常变量
变量前加const,变量变常变量
#define定义的标识符常量
与数组搭配
参考代码,注意换行:#define MAX = 10;int arr[MAX] = {0};
枚举常量(一 一列举的常量)
枚举关键字enum
参考代码,注意换行:enum Sex{MALE,FEMALE,SECRET};
默认排序从0开始
操作符
操作符优先级
基础版,需要应付考试就记这个,考试够用了 <初级版>在初级板块,也会更详细
逻辑运算符
&& 逻辑与/并且
有一个假,就是假;全真才为真
|| 逻辑或
有一个真,就是真,全假为假
! 逻辑非
真的变假的,假的变真的
算术操作符
+
-
*
/
%
%两边必须是整数
赋值操作符
=
+=
a += 20 ~ a = a+20
-=
*=
/=
&=
^=
|=
>>=
<<=
关系操作符
>
>=
<
<=
!= 用于测试"不相等"
== 用于测试"相等"
单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
理解:计算的是变量/类型所占空间的大小,单位字节.
~ 对一个数的二进制取反
按(二进制)位取反
二进制数字: 1010 按位取反=: 0101
-- 自减
a-- //先执行a,执行后a自减1
--a //在执行a之前,先将a-1
++ 自增
与自减同理
* 间接访问操作符(解引用操作符)
顺着地址,修改地址所指向的变量的数据
图示
(类型) 强制类型转换
双目操作符
知道有这么东西!
条件操作符/三目操作符
(exp1? exp2: exp3);
exp1<表达式>
exp2<表达式结果为真,输出exp2>
exp3<表达式结果为假,输出exp3>
移(二进制)位操作符
<<左移位
>>右移位
(二进制)位操作符
& 按位与
| 按位或
^ 按位异或
异或的运算规则:对应的二进制位相同,则为0;对应的二进制位相异,则为1
逗号表达式
exp1,exp2,exp3,……expN
下标引用操作符[ ]
函数调用操作符( )
结构体操作符
.
结构体.成员名
->
结构体指针->成员名
注释
多行注释
/*……*/(C语言注释风格)
缺陷:不支持嵌套注释
单行注释
//(C++注释风格)
循环语句
while循环
for循环
do...while循环
进阶有详细讲
数组
输入格式:<数据类型> <变量名>[<存放数量(必须是常量)>] = {<数据1>,<数据2>,……};
数组通过下标使用
数组的类型
数组:int arr[10]; 类型:int [10];
初级指针
内存
一个内存单元是一个字节
指针变量
代码格式:<数据类型>* <变量名>
一种专门用于存放地址的变量
指针的大小
32位的平台上,一个指针的大小是32个比特,等价于4个字节
64为的平台上,一个指针的大小是64个比特,等价于8个字节
图示
结构体
创建结构体代码格式:strutc <类型名> { 类型数据 };
指针图示
.操作符图示
->操作符图示
:结果
结构体修改
变量修改
数组修改需要使用strcpy
图示(默认使用->演示)
扩展:其他结构体符号修改:
.符号
指针
**<C初级>**
分支与循环
分支语句
if
单分支
if (条件表达式)
双分支
if (条件表达式) else
多分支
if (条件表达式) else if (条件表达式) else
悬空else//else的匹配机制:距离最近的未匹配的if进行匹配
小练习#打印1-100的奇数
for循环版
精简版
特殊 if(表达式) if(表达式)
if...else:语句在执行if语句后,将不会再执行else语句 if...if: 语句->两个if为并列,第一个if执行后,第二个if会继续执行
switch
代码格式:
switch (整形表达式) { 语句项//case (整形常量表达式) 语句 可配合break使用,但不是必须品 }
图示
default//如果输入的数字超出case存储数据的范围,可以default作提示
关于default与case是没有顺序可言的,谁前谁后都OK
代码格式:图示
switch适合作用于多分支语句
switch语句中也可以出现if
注:switch后的整数是几,就从对应的case开始执行
图示
循环语句
while
流程图
break//中止/跳过全部循环
遇到break,完全停止该循环
continue//继续/跳过本次循环
遇到continue,停止输出本次与continue对应的语句,其他的继续循环输出
注:while循环,变量的自增(减)一定要在continue前面,否则会死循环
for
流程图
不可在for循环内修改循环变量,防止for循环失去控制
图示(会死循环)
建议for语句的循环控制变量的取消采用"前闭后开区间"写法
图示
for循环<初始化,条件判断,调整>是可以省略的,但是不要随便省略
for循环可有多个初始化,条件判断,调整
小练习
答案
0次
do...while
流程图
上来先循环,循环后再判断
特点:循环体至少执行一次
拓展:<go to>//无条件跳转语句
练习
计算N的阶乘
代码:
计算1!+2!+3!+.....+10!
代码:
简化:
在一个有序数组中查找具体的某个数字n.编写int binsearch(int x,int v[],int n);功能在v[0]<-v[n]数组中查找x
二分查找算法分析
代码
编写代码,演示多个字符从两端移动,向中间汇聚
图示
编写代码实现,模拟用户登录场景,并且只能登录三次.(只允许输入3次密码,如果密码正确则提示登录成果,如果三次均错误退出程序)
函数
实际参数
真实传给函数的参数,叫实参.实参可以说:常量,变量,表达式,函数等.无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参
形式参数
形式参数是指函数名后括号中的变量,因为形式函数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数.形式参数当函数调用完成之后就自动销毁了,因此形式参数只在函数中有效.
当实参传给形参的时候,形参其实是实参的一份临时拷贝,对形参的修改是不会改变实参的
练习
写个函数,打印100-200之间的素数
输出结果: 101 103 107 109 113 121 127 131 137 139 149 151 157 163 167 169 173 179 181 191 193 197 199
易错点:else下return 1;如果n=9,n%2!=0走else,9不属于素数,程序有问题
写个函数,判断一年是不是闰年
输出结果
写个函数,判断一个整形有序数组的二分查找
函数内部想求参数部分的数组元素的个数,做不到
主函数内<数组>给自定义函数传参,传的不是整个数组,而是该数组中第一个元素的地址!!!
int mid = (left + right) / 2;<----->必须在循环内部,因为二分算法每次查找都会重新赋值中间值(mid)
写个函数,每调用一次这个函数,就将num的值增加1
简单
函数调用
传值调用
直接将变量数值传给自定义函数
传址调用
将变量数值的地址传给自定义函数,通过<解引用操作符>修改变量值
函数的嵌套调用和链式访问
嵌套调用:> <套娃>main函数调用<调用了自定义函数的自定义函数>
不会吧不会吧,不会有人到现在还不知道嵌套吧
链式访问:> <把一个函数的返回值作为另一个函数的参数
printf函数的返回值是:<打印在屏幕上字符的个数>,小小的printf也有大大的奥妙
例:printf("%d",456);---->这个printf的返回值就是3
小测:printf("%d", printf("%d", printf("%d", 43))); 这个printf的输出结果是?
答案:4321
解析:输出逻辑从右往左依次输出,先打印出43,那么第一个printf的返回值就是2,第二部分可以将代码看作:printf("%d", printf("%d", 2)); 如此往复,就打印出了4321
函数的声明和定义
声明
函数的声明就是告诉编译器,一个函数叫什么,参数是什么,返回类型是什么.
正经的函数声明定义,如在vs2019环境下,同一个项目环境下,写一个自定义函数.c文件,创建.h头文件,在.h头文件里声明,进入mian.c文件使用include"自定义函数.h",即可引用函数(可能说不太清楚,但就是这么个道理)
第一步
别光看代码区,也要看左边的面板
注意Add
第二步
在b.c文件里写我们的Add自定义函数,要在同一个工程下
第三步
这是头文件,在头文件里声明你的自定义函数
第四步
使用include"c.h",调用我们已经声明的自定义函数Add,就可以成功打印了
库函数的调用是 include< > ,我们的自定义函数是 include" "
函数的声明一般出现在函数使用之前,要满足先声明后使用
函数的声明一般要放在头文件里
定义
函数的定义是指函数的具体实现,交代函数的功能实现
函数和递归
一个函数,自己调用自己,就叫递归
递归策略只需要少量的程序就可描述出解题过程所需要的多次重复计算,大大的减少了程序的代码量.(把一个大型复杂的问题,层层转化为一个与原问题相似的小问题来解决.)
递归的主要思考方式在与:把大事化小
递归的两个必要条件:>
存在限制条件,当满足这个限制条件的时候,递归便不再继续
每次递归调用之后越来越接近这个限制条件
递归有限制条件,可能会溢出;但如果没限制条件,那么一定会溢出 (因为内存中的栈是有数的,当需要递归的次数大于内存中栈的个数,就会溢出报错,即使有限制条件,写递归需谨慎)
最简单的递归小程序
int main() { main(); return 0; }
栈溢出(stack overflow)
自己去研究三个问题(先把斐波那契搞定,才可以搞定剩下两个问题)
斐波那契
汉诺塔问题
青蛙跳台阶问题
数组
一维数组的创建和初始化
一维数组的使用
数组是一组相同类型元素的集合
代码格式:type_t arr_name [const_n]
人话就是:int arr[10]
=====type//是指数组的元素类型
=====const_n//是一个常量表达式,用来指定数组大小
这样是错误的!!! 数组[]内,count必须是一个常量,否则就GG
在创建数组的同时赋值,就是初始化
int arr[10] = { 1,2,3 };//不完全初始化,下标2后的数值默认为0
char arr[] = { 'a', 'b', 'c' }; printf("%d\n",sizeof(arr)); printf("%d\n",strlen(arr));
第一个printf输出什么? 第二个printf输出什么?
答案
1: 3 2: 随机数
为什么2会打印随机数?因为strlen查找字符串长度是以\0为结束标准的 ,在这组数组里是没有\0的,所以他会一直查找下去,知道找到\0为止
数组是通过下标来访问的,下标是从0开始
数组的大小可以通过:>sizeof(arr)/sizeof(arr[0])来计算
一维数组在内存中的存储
一维数组在内存的存储是连续的
二维数组的创建和初始化
代码格式:>int arr[行][列];
二维数组 >[行]< 可以省略, >[列]< 绝对不可以省略
二维数组也是通过下标来访问的
int arr[3][4]: 0 1 2 3 1 1 2 3 2 1 2 3 三行四列
二维数组在内存中的存储
二维数组在内存中的存储也是连续的
并且,二维数组的每一组,都可以看成一个一维数组
数组作为函数参数
有时候写代码,会将数组作为函数参数.
练习:实现一个冒泡排序,函数将整形数组排序:>
数组名是数组首元素的地址有<两个例外>
1.sizeof(数组名) -- 数组名代表整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节
2. &数组名 -- 数组名代表整个数组; &数组名,取出的是整个数组的地址
printf(arr) == 数组首元素地址 printf(arr[0]) == 数组首元素地址 printf(&arr) == 整个数组的地址
printf(arr+1) == 数组首元素地址+1 == 数组第二个元素的地址 printf(arr[0+1]) == 数组首元素地址+1 == 数组第二个元素的地址 printf(&arr+1) == 整个数组的地址+1 == 下一个数组(直接跳到该数组的末尾,下一组数组的首位)
操作符详解
操作符
C语言运算符优先级
7运算符注意,==/!=是判断符,是输出1,否输出0 那么:>int b = 0; int a = (b == 5); a输出=什么?
答案:1
操作符具体包含请看上图↑↑↑↑↑↑↑↑
算术操作符
/ 号两边都为整型,结果为整型;/ 号两边有一方为实型,结果为double型<双精度实型>
int / int = int
int / double = double double / int = double
% 号两边必须都为整形
5%2 YES
5%2.0 NO
移位操作符
移动的是二进制位;二进制正数最高符号位为0,负数最高符号位为1
右移操作符
移位方式
1.算术右移(通常的,编译器使用)
右边丢弃,左边补符号位;二进制正的符号位是0,负的符号位是1
2.逻辑右移
右边丢弃,左边补0
右移一位 <=> 除个2
整数的二进制有:源码->反码->补码
存储在内存中的是补码,打印出来的是源码
正数的源码、反码、补码是一样的!!!
int a = -1;(这里是负数) 10000000-00000000-00000000-00000001 --> 源码 11111111-11111111-11111111-11111110 --> 反码(源码符号位(首位)不变, 其他按位取反得出反码) 11111111-11111111-11111111-11111111 --> 补码(反码+1得出补码)
一个16进制的f == 四个2进制的1
左移操作符
左边丢弃,右边补0
左移一位 <=> 乘个2
符号位不变
警告:不要移动负数位,这是标准未定义的。
例如: int a = 2; a >> -1;
位操作符
位:2进制位
他们的操作数必须是整数
& 按位与
一串二进制数据中,互相为1则为1,有一个为0则为0
00000000-00000000-00000000-00000011 --> 3 00000000-00000000-00000000-00000101 --> 5 00000000-00000000-00000000-00000001 --> 3&5=1 有0有1则为0,全1才是1
| 按位或
一串二进制数据中,互相为1则为1,有一个为1也为1,都是0才是0
00000000-00000000-00000000-00000011 --> 3 00000000-00000000-00000000-00000101 --> 5 00000000-00000000-00000000-00000111 --> 3|5=7 全1为1,有0有1也为1。
^ 按位异或
一串二进制数据中,相同为0,相异为1。
00000000-00000000-00000000-00000011 --> 3 00000000-00000000-00000000-00000101 --> 5 00000000-00000000-00000000-00000110 --> 3^5=6 相同为0,相异为1。
小练习:在不创建第三个变量的前提,将a=3;b=5;交换得出a=5;b=3;
int a = 3; int b = 5; //加减法 a = a + b; b = a - b; a = a - b;
缺点:如果a或b太大,相加会栈溢出
int a = 3; int b = 5; //异或法 a = a ^ b; b = a ^ b; a = a ^ b;
小练习:求一个整数存储在内存中的二进制中1的个数
赋值/复合赋值操作符
简单,没得写!
单目操作符
简单,不细讲
sizeof//
int arr[10] = {0}; 这个数组的类型是: int [10]
int arr[10]; sizeof(int [10])//求出数组所占用的字节<=>sizeof(arr)==40
sizeof(内的表达式是不参与运算的);
数组传参传的是首元素的地址,sizeof(<接收:首元素的地址>);32位打印4,64位打印8;
void test(int arr[]) { printf("%d\n", sizeof(arr)); } void test2(char ch[]) { printf("%d\n", sizeof(ch)); } int main() { int arr[10] = { 0 }; char ch[10] = { 0 }; printf("%d\n", sizeof(arr)); printf("%d\n", sizeof(ch)); test(arr); test2(ch); return 0; }
输出结果是?
40, 10, 4/8, 4/8 如果你看不懂这串答案,说明你不懂这串代码
~按位取反
a 00000000000000000000000000000000 ~a 11111111111111111111111111111111
与左移右移的区别,左移右移不会改变符号位: 按位取反会改变符号位。
int main() { int a = 11; //00000000000000000000000000001011 <-11的二进制 //00000000000000000000000000000100 <-1的二进制左移二位]->:4 //00000000000000000000000000001111 <-使用按位或[ | ]全0为0,有1则1]->:15 a = a | (1 << 2); printf("%d\n", a);//输出15 /*=!=!=!=!=!=!=!=!=!=!=!=!=!=!=!=!=!分隔=!=!=!=!=!=!=!=!=!=!=!=!=!=!=!=!=!=!=!*/ //00000000000000000000000000000100 <-1的二进制左移二位]->:4 //11111111111111111111111111111011 <-将4按位取反 //00000000000000000000000000001111 <-现在的a]->:15 //00000000000000000000000000001011 <-使用按位与[ & ]全1为1,有0则0]->:11 a = a & (~(1 << 2)); printf("%d\n", a);//输出11 return 0; }
关系操作符
判断相等时用两个等号//==
逻辑操作符
注意一点《短路原则》
int main() { int i = 0, a = 0, b = 2, c = 3, d = 4; i = a++ && ++b && d++; printf("a = %d\n b = %d\n c = %d\n d = %d\n", a, b, c, d); return 0; }
输出什么?
1234
短路原则:&&中,只要有一个为假,结果必定是0。左边为假,右边就不再执行。 ++b // d++这些条件都没有执行,所以是1234
再想,||中,只要有一个真,结果必定是1,左边为真,右边就不再执行。
条件操作符
exp1 ? exp2 : exp3
逗号表达式
从左向右依次执行。整个表达式的结果是最后一个表达式的结果
exp1, exp2, exp3, ..., expn
下标引用
操作数:一个数组名+一个索引值
[ ]
函数调用
( )
简单
结构成员
struct//结构体关键字(C基础-常见关键字里也有)
.
->
该符号,使用前必须建立一个指针
去看C基础的结构体
表达式求值
隐式类型转换 (会有精度丢失)
《整型提升》 这玩意比较麻烦,但挺简单的 简单来说就是,类型小于int类型的其他类型在运算的时候会在内存里转换成int类型的二进制求解
计算都用int,如果你用char去算两数的相加呢? char a = 3; //char是一个字节=8个bit,3转成二进制就是00000011 char b = 127; //127 -> 011111111 char c = a+b; //char是怎么计算的呢?这里就用到了整形提升,他会把char提成int,1个字节变4个字节=32bit c是多少? 看到这里你就应该想起之前笔记写的char的范围-128~127
int main() { //整型提升实例: char a = 1; printf("%u\n", sizeof(a)); printf("%u\n", sizeof(+a)); printf("%u\n", sizeof(!a)); return 0; }
答案?:
分别打印 1 4 1
因为打印(+a)时,char a会发生整型提示,整型不就是4个字节么,所以打印4.
什么时候需要用到整型提升: 只要是,参与运算的类型大小没有达到整形的大小,就会进行整形提升。 如:char 类型 short 类型
《算术转换》 类型大于等于int的类型 在计算的时候,会先将较小的类型转换成较大的类型来计算
int + float == (float)int + float //此代码为方便理解,并非真实代码
操作符的属性
优先级
结合性(确定优先级后,才考虑结核性)
是否控制求值顺序
图里都有
但要注意不要写出问题代码:a*b + c*d + e*f
这是串问题代码,优先级、结合性混乱,表达式结果不稳定,还不方便读,写出来就是一坨SHI
表达式,所表达的结果,必须是唯一的,计算路径一定是唯一的!!!
int main() {//非法表达式 int i = 10; i = i-- - --i * (i = -3) * i++ + ++i; printf("i = %d\n", i); return 0; }//代码下午写的,人上午失踪的
nb一点的初级指针(Pointer)
指针是编程语言中的一个对象,利用地址,他的值能直接指向存在电脑存储器中另一个地方的值。通过它能找到以它为地址的内存单元
一个内存单元是一个字节
指针是用来存放地址的,地址是唯一标示一块地址空间的
指针的大小在32位平台上是4个字节,64位平台上是8个字节
指针和指针类型
指针类型的意义:指针类型决定了指针进行解引用操作的时候,能访问空间的大小 int* p; *p 能访问4个字节 char* p; *p 能访问1个字节 double* p; *p 能访问8个字节
指针 + - 整数:指针类型决定了,指针+1向后跳几个字节 int* p; p+1 跳4个字节 char* p; p+1 跳1个字节 double* p; p+1 跳8个字节
int main() { int a = 0x11223344; int* pa = &a; char* pc = &a; printf("%p\n", pa); printf("%p\n", pa + 1); printf("%p\n", pc); printf("%p\n", pc+1); return 0; }
野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
指针未初始化
int* p; 不行! int* p = NULL; 行
指针越界访问
一个存储10个元素的数组,指针依次取地址,到取第11个元素的时候,这个地址是随机的,越数组的界了,违法了。
指针指向的空间释放
给你个地址,你去了发现房子被拆了,这个地址还有用吗
如何规避野指针
指针初始化
小心指针越界
指针指向空间释放,即使指针重置NULL(为空)
int* p = NULL;
指针使用之前检查有效性
int main() { int a = 10; int* pa = &a; //pa = NULL; if (pa != NULL) { *pa = 20; printf("%d\n", *pa); } else printf("sorry!"); return 0; }
指针运算
指针 +- 整数
指针 - 指针
得到中间的元素个数
指针的关系运算<比较大小>
int main() { float arr[N]; float* p; for (p = &arr[N]; p > &arr[0];) { *--p = 0; } return 0; }
int main() { float arr[N]; float* p; for (p = &arr[N]; p > &arr[0]; p--) { *p = 0; } return 0; }
第二种更容易理解,但真正使用建议使用第一种,因为第二种(p--会导致指针越界)是不被C标准承认的
指针和数组
去复习一下数组
for(=======) printf("%d",arr[i]); printf("%d",*(p+i)); 数组和指针的应用,这两个打印出来的是一样的
二级指针
int a = 10; int* p1 = &a; 解引用操作符* int** p2= &p1;//这玩意就是二级指针 解引用操作符*变** int*** p3 = &p2;//三级指针 解引用操作符变*** int**n pn = &pn-1;//n级指针,以此类推 解引用操作符变n个*
就套娃,一点难度没有
指针数组
指针数组 -> 本质是数组:>是指存放指针的数组
int arr[] = { 0 }; //整形数组 int* arr[] = { &a }; // 指针数组
nb一点的初级指针,就结束了
结构体
结构基础知识:结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的质量。
结构体类型的声明
声明
方法1 struct Stu { 成员列表 }全局变量;
方法2 typedef struct Stu { 成员列表 }重定义类型名;
代码! 建议放到编译器里看
//写法1: //struct-结构体关键字/Stu-结构体标签/struct Stu-是结构体类型 struct Stu { //成员变量 char name[20]; short age; char tele[12]; char sex[5]; }s1, s2, s3;//因为这是一条结构体类型声明语句,所以必须加上分号。 //s1 s2 s3 是结构体全局变量! //写法2 typedef struct Stu { //成员变量 char name[20]; short age; char tele[12]; char sex[5]; }Stu;//tupedef 是类型重定义,直接将类型名重定义为Stu //这里的Stu是类型 上面的s1 s2 s3 是变量 int main() { struct Stu s;//这个s s4 是结构体局部变量 Stu s4; return 0; }
结构体成员的类型:标量(标准变量),数组,指针,甚至是其他结构体。
结构体初始化
struct S { int a; char c; char arr[20]; double b; }; struct T { char ch[10]; struct S s; char* pc; }; int main() { char arr[] = "hello world"; struct T t = { "hello",{100,"w","hello wold",3.14},arr };//结构体初始化 printf("%s\n", t.ch); printf("%s\n", t.s.arr); //想用 -> 需要建立一个指针,具体看笔记 return 0; }
结构体成员访问
结构体传参
代码
//结构体成员访问及传参 typedef struct Stu { char name[20]; short age; char phone_num[12]; char sex[5]; }Stu; void print1(Stu tmp) { printf("name :%s\n", tmp.name); //我懒,后面懒得写了,都一样 } void print2(Stu* p) { printf("name :%s\n", p->name); //我懒,后面懒得写了,都一样 } int main() { Stu s = { "张三",22,"12345678911","男" }; print1(s); printf("\n"); print2(&s); //问:print1 和 print2 那个好? //答:print2 好。因为print2栈的占用更少 return 0; }
<作业>
作业1
答案
b
答案
a
答案
C----define是预处理指令
答案
B
答案
c
答案
8,10,12,14,16 static作用:增加局部变量生命周期,减少全局变量作用域,改变函数链接属性. 第一轮循环下来,输出一个8,sum函数内b=5,因static函数作用,增加局部变量b生命周期,sum函数执行结束,局部变量b开辟内存空间未被销毁,第二轮循环进入b在=5的基础上再+2 =7, printf输出10
作业2
答案
死循环打印5 注意 if (i = 5) 一个=是赋值符号 两个==才是等于
答案
D肯定错了,其他三个答案不算错,但题就这么出的,玩文字游戏了.(为什么?记得悬空else吗?) 第一题用了只能,语气表达过于绝对;既然A那么吊,所以他就错了.(其实是如果 if(表达式) 后加 { } 的话, { } 内可以有很多语句) 第二题if语句0为假,非零为真;一般计算机在输出时,默认输出1.错了吗?没错,错的是这个世界! 答案是C....其实也能挑刺,单分支,多分支,我的双分支哪儿了?
答案
C
答案
D
答案
D int/long大家都知道是(长)整形,那char呢?char字符是以ascii码存在内存里的,对应的也是整形数字啊
答案
hellothird 没做多余注释的都是超级简单的,如果你发现你不会做,要么是间歇性失明,要么就是心早就飞了,再要么就是你不会走就想跑,建议去背几个单词再来看.
写代码将3个数从大到小输出
答案
int main() { int a, b, c; printf("please input there number:>"); scanf("%d %d %d", &a, &b, &c); if (a < b) { int tmp = a; a = b; b = tmp; } if (a < c) { int tmp = a; a = c; c = tmp; } if (b < c) { int tmp = b; b = c; c = tmp; } printf("%d,%d,%d\n", a, b, c); return 0; }
打印1-100之间所有3的倍数的数字
答案
int main() { int i = 0; for (i = 1; i <= 100; i++) { if (i % 3 == 0) { printf("%d ", i); } } return 0; }
给两个数,求这两个数的最大公约数
答案
int main() { int m = 24; int n = 18; int r = 0; while (r = m % n) { m = n; n = r; } printf("%d ", n); return 0; }
确实有些复杂,文字描述不清楚,实在看不懂,百度辗转相除法
打印1000年到2000年之间的闰年 //如何判断闰年:能被4整除的,不能被100整除的,就是闰年 //还有哦,能被400整除的,也是闰年
答案
int main() { //如何判断闰年:能被4整除的,不能被100整除的,就是闰年 //还有哦,能被400整除的,也是闰年 int i = 0; for (i = 1000; i <= 2000; i++) { if (i % 4 == 0 && i % 100 != 0 || i%400==0) { printf("%d ", i); } } return 0; }
打印100->200之间的素数
答案
int main() { int count = 0; int i = 0; for (i = 100; i <= 200; i++) { int a = 0; for (a = 2; a < i; a++) { if (i % a == 0) { break; } } if (a == i) { count++; printf("%d ", i); } } printf("\ncount =%d \n", count); return 0; }
试除法
作业3
答案
B
答案
C
编写代码数一下1-100的所有整数中出现了多少个数字9
答案
int main() { int i = 0; for (i = 1; i <= 100; i++) { if (i % 10 == 9) { //个位为9 printf("%d ", i); } if (i / 10 == 9) { //十位为9 printf("%d ", i); } } return 0; }
答案
#include<stdio.h> int main() { int i = 0; for (i = 1; i <= 100; i++) { if (i % 10 == 9) { printf("%d ", i); } } return 0; }
D 2!=5进递归3!=5进递归4!=5进递归5==5返回2 2*2=4返回4,2*4=8返回8,2*8=16返回16 递归结束
C
void Table(int a) { int i = 0; for (i = 1; i <= a; i++) { int j = 0; for (j = 1; j <= i; j++) { printf("%d*%d=%d\t", i, j, i * j); } printf("\n"); } } int main() { int i = 0; scanf("%d", &i); Table(i); return 0; }
////第一种 sizeof 法 //void Pz(char* arr, int sz) //{ // int left = 0; // int right = sz; // while (left < right) // { // int tmp = arr[left]; // arr[left] = arr[right]; // arr[right] = tmp; // left++; // right--; // } //} // //int main() //{ // char arr[] = "abcdef"; // int Sz = sizeof(arr) / sizeof(arr[0]) - 2; // Pz(arr, Sz); // printf("%s", arr); // return 0; //} ////第二种 my_strlen 法 //int my_strlen(char* arr) //{ // int count = 0; // while (*arr != '\0') // { // count++; // arr++; // } // return count; //} // //void Pz(char* arr) //{ // int left = 0; // int right = my_strlen(arr) - 1; // while (left < right) // { // int tmp = arr[left]; // arr[left] = arr[right]; // arr[right] = tmp; // left++; // right--; // } //} // //int main() //{ // char arr[] = "abcdef"; // Pz(arr); // printf("%s", arr); // return 0; //} //方法三 递归 int my_strlen(char* arr) { int count = 0; while (*arr != '\0') { count++; arr++; } return count; } void Pz(char* arr) { int tmp = arr[0]; int len = my_strlen(arr); arr[0] = arr[len - 1]; arr[len - 1] = '\0'; if (my_strlen(arr + 1) >= 2) { Pz(arr + 1); } arr[len - 1] = tmp; } int main() { char arr[] = "abcdef"; Pz(arr); printf("%s", arr); return 0; }
int Di(unsigned int num) { if (num > 9) { return Di(num / 10) + num % 10; } else { return num; } } int main() { unsigned int num = 0; scanf("%d", &num); int ret = Di(num); printf("ret = %d\n", ret); return 0; }
n^k = n*n^(k-1) n^0 = 1
double Pow(int n, int k) { if (k < 0) { return (1.0 / (Pow(n, -k))); } else if (k == 0) { return 1; } else return n * Pow(n, k - 1); } int main() { int n = 0; int k = 0; scanf("%d%d", &n, &k); double ret = Pow(n, k); printf("ret = %lf\n", ret); return 0; }
作业4
答案:A,return 只能返回一个数
C
B
答案:B D将声明改成<定义>就对了
C
B.4 逗号表达式怎么描述的来着?
内聚:社交恐惧症 耦合:社交牛逼症 答案:B 尽量少使用全局变量
C
C
栈保存<局部变量和形参> 答案:D
作业5
C
B
C
B
B (3,4)是一个逗号表达式,逗号表达式从左往右,整个表达式的结果就是最后一个表达式的结果。
A sizeof计算字符串字节,会把\0也算进去 strlen计算字符串长度,遇到\0就停了,不会把\0算进去
D
#include<stdio.h> void Reset(int arr[], int sz) { //数组初始化为0 int i = 0; for (i = 0; i < sz; i++) { arr[i] = 0; } } void Print(int arr[], int sz) { //使用Print打印数组 int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } void Reverse(int arr[], int sz) { //数据交换 int left = 0; int right = sz - 1; int i = 0; while (left < right) { int tmp = 0; tmp = arr[left]; arr[left] = arr[right]; arr[right] = tmp; left++; right--; } } int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int sz = sizeof(arr) / sizeof(arr[0]); //Reset(arr, sz); Print(arr, sz); Reverse(arr, sz); Print(arr, sz); return 0; }
作业6
D
D 该题在不同的编译器里结果不同,表达式丢失唯一性
C
C
B
C char* 只能操作一个一个字节,int有4个字节。
A 全局变量不初始化,默认为0
sizeof//计算变量类型所占字节的大小,是恒>=0的,没有人听说过C语言那个类型占用-4个字节。 正数算无符号数,i是-1算有符号数。 当一个整数与无符号数进行运算时,会把整数先转换成无符号数。 -1转无符号数会变成一个很大的数,最起码比4大。
-1 10000000 00000000 00000000 00000001//源码 11111111 11111111 11111111 11111110//反码 11111111 11111111 11111111 11111111//补码 转换成无符号数,正数源码,反码,补码相同,这一排一得多大,肯定比4大
B 绕死你
A
#include<stdio.h> ////方法一 //int Pize1(unsigned int x) //{ // //采用无符号方法计算 // int count = 0; // while (x) // { // //简单的%2 /2 // if (x % 2 == 1) // { // count++; // } // x = x / 2; // } // return count; //} ////方法二 //int Pize1(int a) //{ // //采用按位方法计算 // int count = 0; // int i = 0; // for (i = 0; i < 32; i++) // { // if (((a >> i) & 1) == 1) // {//右移一位相当于 除个2 // count++; // //设x==13 // //13 (正数:补码 反码 源码>一样) // //00000000000000000000000000001101 // //算术右移:右边丢弃,左边补符号位 // //00000000000000000000000000001101 // //& 1 // //00000000000000000000000000000001 // //00000000000000000000000000000001 // // == 1 进入count++; // //如此执行下去,i++ == 1,右移一位 == 0 不进入count // } // } // return count; //} //方法三 int Pize1(int x) { int count = 0; while (x) { //设x==13 //13 :> 1101 //& //12 :> 1100 // 一组 //12 :> 1100 //& //11 :> 1011 // 二组 //8 :> 1000 //& //7 :> 0111 // 三组 //0 :> 0000 //为0,不进入循环,输出3 x = x & (x - 1); count++; } return count; } int main() { int a = 0; scanf("%d", &a); //写一个函数,求二进制数列中有几个1. int count = Pize1(a); printf("count = %d", count); return 0; }
int Pize(int m, int n) { //13 //1101 //12 //1100 //一位不同 //13 //1101 //8 //1000 //二位不同 //1999 //0111 1100 1111 // ^ //2299 //1000 1111 1011 // = //1111 0011 0100 //7个1;七位不同 int tmp = m ^ n; int count = 0; while (tmp) { //这里套用的是上一道题,输出二进制中一的个数 tmp = tmp & (tmp - 1); count++; } return count; } int main() { int m = 0; int n = 0; scanf("%d%d", &m, &n); int count = Pize(m, n); printf("count = %d", count); return 0; }
int main() { int a = 5; int b = 10; a = a^ b; b = b^ a; a = a ^ b; printf("%d %d", a, b); return 0; }
void Arr(int* p, int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", *(p+i)); } } int main() { int arr[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int sz = sizeof(arr) / sizeof(arr[0]); Arr(arr, sz); return 0; }
**<C进阶>**
数据的存储
数据类型详细介绍
C语言类型
内置类型
int float char long short double 等等
类型的基本归类
整形家族
char存的字符,是ascii,所以存到整形里 [int]可以省略
浮点数家族
float double
自定义类型/构造类型
数组类型
数组名去掉就是类型 int arr[10]; int[10]就是他的类型
结构体类型 struct
枚举类型 enum
联合类型 union
特殊类型
指针类型
int* p; char* p; void* p;
void* 类型的指针,可以接受任意类型的地址; void* 类型的指针不能进行解引用操作,因为他没有集体类型 void* 类型的指针不能进行加减整数的操作
void空类型
void表示空类型(无类型)
一般用在函数的返回类型,函数的参数,指针类型
类型的意义
开辟内存空间大小
看待内存空间的视角
整形在内存中的存储
源码,反码,补码
计算机中,有符号整数有三种表示方法,源码,反码,补码;无符号整数则 源 反 补码相同
三种表示均有符号位和数值位两部分,符号位用0表示正数,用1表示负数,而数值位三种表示方法各不相同
源码:直接按照正负数的形式翻译成二进制
反码:将源码符号位不变,其他位依次按位取反
补码:反码+1
整型在内存中存储的是二进制的补码
大小端字节序介绍及判断
大小端
什么是大小端:
大端(字节序存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中
0x 11 22 33 44 低地址<---------->高地址
大端存储
小端(字节序存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中
0x 44 33 22 11 低地址<---------->高地址
小端存储
什么是高位,什么是低位 123--3是个位,就是低位 0x11 22 33 44,44就是低位,11就是高位
VS编译器就是小端存储
为什么会有大端和小端:
简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序
#include<stdio.h> //简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序 //指针的类型决定了指针能访问几个字节 //指针类型的意义: //1.指针类型决定了指针解引用操作符能访问几个字节:char* p; *p; 访问了1个字节 int* p; *p; 访问了4个字节 //2.指针类型决定了指针+1,-1 加或减的是几个字节;char* p; p+1;跳过1个字节 int* p; p+1; 跳过4个字节(一个整形) //第一种 //int main() //{ // int a = 1; // char* p = (char*)&a; // if (*p == 1) // { // printf("小端\n"); // } // else // { // printf("大端\n"); // } // return 0; //} //第二种 //int check_sys() //{ // //方便理解 // int a = 1; // char* p = (char*)&a; // //return 1 小 // //return 0 大 // return *p; //} int check_sys() { int a = 1; return *(char*)&a; } int main() { int ret = check_sys(); ret == 1 ? printf("小端\n") : printf("大端\n"); return 0; }
小练习
-1 -1 255
提示:整形提升
4294967168
其实a = 128 输出的结果和-128一样
-10
int main() { int a = -20; unsigned int b = 10; //1000 0000 0000 0000 0000 0000 0001 0100 -20 源码 //1111 1111 1111 1111 1111 1111 1110 1011 反码 //1111 1111 1111 1111 1111 1111 1110 1100 补码 负数内存存储补码 //0000 0000 0000 0000 0000 0000 0000 1010 10 正数原反补相同 //1111 1111 1111 1111 1111 1111 1111 0110 -20+10的补码 //1111 1111 1111 1111 1111 1111 1111 0101 反码 //1000 0000 0000 0000 0000 0000 0000 1010 源码 //-20+10 = -10 printf("%d\n", a + b); return 0; }
9 8 7 6 5 4 3 2 1 0 死循环
255
死循环
unsigned char(无符号char)范围0-255,条件恒成立,死循环
浮点型在内存中的存储解析 (整形和浮点型在内存的存储方式是不一样的 如:以整形的方式存入一串数字,浮点型的方式取出这串数字)
浮点家族:float,double,long double,等
****浮点型在内存中存储的方法****
放入
练习:
int main() { float a = 48.5; // 48.5 // 00110000.1 (0.5的二进制是0.1) // (-1)^0 * 1.100001 * 2^5 // S M E // 0 132 100001 // 0 10000100 10000100000000000000000 // 0100 0010 0100 0010 0000 0000 0000 0000(内存中存储的浮点型二进制) // 0x 42420000(调式地址以十六进制显示) return 0; }
取出
分析该段代码
指针的进阶
字符指针
两种方法皆可
arr1,arr2地址不同 p1,p2地址相同
Segmentation fault//段错误
看到这里翻回去看一看”栈溢出“报的什么错
char *p = "abcde"
p是一个指针变量,存不下一个字符串,所以p里存的是a的地址,且这个字符串是常量字符串,不可修改,为了方便调试,建议char前加一个 const
指针数组
int* arr[ ];//存放整形指针的数组 - 指针数组 char* arr[ ];//存放字符指针的数组 - 指针数组
该写法方便理解 但实际很龊
指针数组一般情况下这样使用
int* arr[5];
arr是一个数组,5个元素,每个元素是 Int*
数组指针
考虑*号优先级 P1先和数组结合,再有int*,是指针数组 P2先和指针结合,再有[10],是数组指针
int arr[5]; int (*p)[5] = &arr; ----他的数组指针类型是:> int (*)[5]
char* arr[5]; ( )它的数组指针怎么写?
char* (*p)[5] = &arr;
&数组名VS数组名 (关于该部分,不清楚的可以往上翻,笔记都有)
数组名绝大多数是首元素地址
&数组名 是数组的地址
数组指针的使用 (数组指针在二维数组以上时使用,才会感觉到便利)
int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};是一个二维数组 只要是数组,arr就是首元素的地址,在这串二维数组中,谁是首元素的地址? 将这个二维数组,看做3个一维数组,第一个一维数组元素,就是二维数组的首元素{1,2,3,4,5}
二维:*(*(p+i)+j) == p[i][j] 一维:arr[i] == *(arr+i) == *(p+i) == p[i]
二维
一维
int (*parr3[10])[5];
[ ] 优先级比 * 高,所以parr3先与 [ ] 结合,parr3[10]是个数组,有十个元素,每个元素又是一个数组指针 去掉数组名,他的类型是 int(*)[5];
数组传参和指针传参
在写代码时难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计?
数组传参
一维数组传参
二维数组传参
指针传参
一级指针传参
思考:当一个函数的参数部分为一级指针的时候,函数能接受什么参数? 例: 1. void test1(int *p) test1接收什么参数 2. vodi test2(char* p) test2接收什么参数
二级指针传参
思考:当一个函数的参数部分为二级指针的时候,函数能接受什么参数? 可以传一级指针变量的地址,也可以传二级指针变量本身,还可以传存放 一级指针数组的数组名
函数指针
数组指针是指向数组的指针 函数指针是指向函数的指针
函数指针,是一个指针,他是用来存放函数地址的指针。
函数的地址
注: int arr[10] = { 0 }; //&arr 和 arr 不一样;但对于函数来说,是一模一样的,都是函数的地址
问题:函数的地址如果要存,往那存?
(*p)(int, int)的使用
拓展
* 可有可无,Add将地址传给p;p==Add
不同的函数参数,函数指针的参数也是不相同的
阅读两端代码
(*(void(*)())0)()
将 void(*)() 这个函数指针加上 强制类型转换 () 得到 (void(*)())0 将0作为函数的地址 再加上 * 解引用操作符 用()保证优先结合性 最后的()就是函数参数,由于函数是void,所以无参 拆开来看: ( * ( void(*)() ) 0 )(函数参数); 保证优先结合性 解引用操作符 函数指针 强制类型转换 函数地址给0
void(* signal(int, void(*p)(int)))(int);
signal是一个函数,int和void(*p)(int)是这个函数的参数;除去这些,就剩下个 void(*)(int),作为函数的返回类型
如何简化,使用typedef关键字
注意:typedef在函数指针的代码格式
函数指针数组
看图理解
int (*p[4])(int, int) = { Add, Sub, Mul, Div };函数指针数组
练习
函数指针数组的用途:转移表
计算器
实现同样功能的两段代码
未使用函数指针数组
void menu() { printf(" ******************\n"); printf("** 1.加法 2.减法 **\n"); printf("** 3.乘法 4.除法 **\n"); printf("** 5.按位与 6.按位或 **\n"); printf("** 7.左移 8.右移 **\n"); printf("***9.按位异或 0.退出***\n"); printf(" ******************\n"); } int Add(int x, int y) { return x + y; } int Sub(int x, int y) { return x - y; } int Mul(int x, int y) { return x * y; } int Div(int x, int y) { return x / y; } int And(int x, int y) { return x & y; } int bOR(int x, int y) { return x | y; } int left(int x, int y) { return x << y; } int right(int x, int y) { return x >> y; } int Xor(int x, int y) { return x ^ y; } int main() { int input = 0; int x = 0; int y = 0; menu(); do { printf(" *请选择菜单键:>"); scanf("%d", &input); switch (input) { case 0: printf(" *退出"); break; case 1: printf(" *请输入操作数:>"); scanf("%d%d", &x, &y); printf(" *%d\n", Add(x, y)); break; case 2: printf(" *请输入操作数:>"); scanf("%d%d", &x, &y); printf(" *%d\n", Sub(x, y)); break; case 3: printf(" *请输入操作数:>"); scanf("%d%d", &x, &y); printf(" *%d\n", Mul(x, y)); break; case 4: printf(" *请输入操作数:>"); scanf("%d%d", &x, &y); printf(" *%d\n", Div(x, y)); break; case 5: printf(" *请输入操作数:>"); scanf("%d%d", &x, &y); printf(" *%d\n", And(x, y)); break; case 6: printf(" *请输入操作数:>"); scanf("%d%d", &x, &y); printf(" *%d\n", bOR(x, y)); break; case 7: printf(" *请输入操作数:>"); scanf("%d%d", &x, &y); printf(" *%d\n", left(x, y)); break; case 8: printf(" *请输入操作数:>"); scanf("%d%d", &x, &y); printf(" *%d\n", right(x, y)); break; case 9: printf(" *请输入操作数:>"); scanf("%d%d", &x, &y); printf(" *%d\n", Xor(x, y)); break; default: input = 0; printf(" *输入错误。"); break; } } while (input); return 0; }
main函数内,可使用回调函数优化
void Cala(int (*p)(int, int)) { int x = 0; int y = 0; printf(" *请输入操作数:>"); scanf("%d%d", &x, &y); printf(" *%d\n", p(x, y)); } int main() { int input = 0; menu(); do { printf(" *请选择菜单键:>"); scanf("%d", &input); switch (input) { case 0: printf(" *退出"); break; case 1: Cala(Add); break; case 2: Cala(Sub); break; case 3: Cala(Mul); break; case 4: Cala(Div); break; case 5: Cala(And); break; case 6: Cala(bOR); break; case 7: Cala(left); break; case 8: Cala(right); break; case 9: Cala(Xor); break; default: input = 0; printf(" *输入错误。"); break; } } while (input); return 0; }
使用函数指针数组
void menu() { printf(" ******************\n"); printf("** 1.加法 2.减法 **\n"); printf("** 3.乘法 4.除法 **\n"); printf("** 5.按位与 6.按位或 **\n"); printf("** 7.左移 8.右移 **\n"); printf("***9.按位异或 0.退出***\n"); printf(" ******************\n"); } int Add(int x, int y) { return x + y; } int Sub(int x, int y) { return x - y; } int Mul(int x, int y) { return x * y; } int Div(int x, int y) { return x / y; } int And(int x, int y) { return x & y; } int bOR(int x, int y) { return x | y; } int left(int x, int y) { return x << y; } int right(int x, int y) { return x >> y; } int Xor(int x, int y) { return x ^ y; } int main() { int input = 0; int x = 0; int y = 0; menu(); do { printf(" *请选择菜单键:>"); scanf("%d", &input); int(*p[])(int, int) = { 0,Add,Sub,Mul,Div,And,bOR,left,right,Xor }; if (input >= 1 && input <= 9) { printf(" *请输入操作数:>"); scanf("%d%d", &x, &y); printf(" *%d\n", (*p[input])(x, y)); } else if (input == 0) { printf(" *退出循环\n"); } else { printf(" *选择错误\n"); } } while (input); return 0; }
指向函数指针数组的指针
看的我指字都不会写了
这玩意就是绕,真没啥难度
回调函数
较复杂,无能力做讲解与补充;学海无涯,长路漫漫,自求多福吧
qsort函数的使用
//qsort 函数的使用 //int my_cmp_int(const void* e1, const void* e2) //{ // return (*(int*)e1 - *(int*)e2); //} // //int my_cmp_float(const void* e1, const void* e2) //{ // return (int)(*(float*)e1 - *(float*)e2); //} // //struct Stu //{ // char name[20]; // int age; //}; //int my_cmp_Stu_name(const void* e1, const void* e2) //{ // return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name); //} //int my_cmp_Stu_age(const void* e1, const void* e2) //{ // return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age; //} // //int main() //{ // int int_arr[] = { 5,4,6,7,9,8,2,1,3,0 }; // // float float_arr[] = { 5.0,4.0,6.0,7.0,9.0,8.0,2.0,1.0,3.0,0.0 }; // // struct Stu s[3] = { {"zhangsan",20},{"lisi",30},{"wangwu",10} }; // // int sz = sizeof(s) / sizeof(s[0]); // qsort(s, sz, sizeof(s[0]), my_cmp_Stu_age); // // int i = 0; // int j = 0; // for (i = 0; i < sz; i++) // { // printf("%f ", s[i]); // } // return 0; //}
自定义 "qsort" 函数
typedef struct Stu { char name[20]; short age; }srr; void Swap(char* a, char* b, int width) { int i = 0; for (i = 0; i < width; i++) { char tmp = *a; *a = *b; *b = tmp; a++; b++; } } void buble_sort(void* source,int sz,int width,int (*my_cmp_age)(void* a, void* b)) { int i = 0; for (i = 0; i < sz - 1; i++) { int j = 0; for (j = 0; j < sz - 1 - i; j++) { //比较***难 if (my_cmp_age((char*)source + j * width, (char*)source + (j + 1) * width) > 0) { //交换 Swap((char*)source + j * width, (char*)source + (j + 1) * width, width); } } } } int my_cmp_age(void* a, void* b) { return ((srr*)a)->age - ((srr*)b)->age; } int my_cmp_name(const void* a, const void* b) { return strcmp(((srr*)a)->name, ((srr*)b)->name); } int main() { srr s[3] = { {"zhangsan",23},{"lisi",40},{"wangwu",26} }; int sz = sizeof(s) / sizeof(s[0]); buble_sort(s, sz, sizeof(s[0]), my_cmp_name); return 0; }
指针和数组面试题的解析
第一部分
一维数组
字符数组
二维数组
总结
第二部分//笔试题
2 5
0x 00 10 00 14 0x 00 10 00 01 0x 00 10 00 04
10,5
字符串+内存函数的介绍
https://cplusplus.com/reference/clibrary/ c\c++库,有不懂的看文档
函数介绍
求长度字符串
strlen
strlen的返回值类型是 size_t == unsigned int //无符号整形
my_strlen
长度不受限制的字符串函数 结束只认 '\0'
strcpy//字符串拷贝
strcpy拷贝时,会将 '\0' 同时拷贝过去
my_strcpy
strcat//字符串追加
自己给自己追加会死循环
my_strcat
注: while (*str3 != '\0') { str3++; } 不可以写成 while (*str3++) { ; } 前、后置++都会影响最终结果
strcmp//字符串比较
重:
大于0的数,指的不只是1 小于0的数,指的不只是-1
my_strcmp
这种有点龊,但是比较好理解
优解
VS>my_strcmp实现
gcc>my_strcmp实现
长度受限制的字符串函数 多了个 n <num的简写>
strncpy//可以控制的字符串拷贝
当num>串2,多余位自动补'\0'
自实现
操作符优先级
strncat//可以控制的字符串追加
会主动加一个 '\0'
个数比串长,把元素搞过去补个'\0'就不管了
自实现
C标准库函数
strncmp//可以控制的字符串比较
输出>0的数
自实现
C标准库函数
打开看了一眼…… 算了还是自己写吧。 但人家的代码肯定比我健壮 我的代码仅供参考
字符串查找 strchr//字符查找
strstr
注意看类型 char* 返回的是一个地址 找到,返回地址;找不到,返回空指针
:def
:defghi
:子串不存在
:defabcdef 返回第一次出现的地址
my_strstr
了解KMP算法
C库代码
建议安装个Everything,查找库函数观看
strtok
字符操作
tolower:大写转小写 toupper:小写转大写
错误信息报告
strerror
将错误码,对应翻译成错误信息
内存操作函数
memcpy//内存拷贝
size_t num单位字节
my_memcpy
memmove//内存移动 处理内存重叠拷贝的情况
my_memmove
memcmp//内存比较
memset//内存设置
自定义类型详解(结构体+枚举+联合)
int float char 等... 都是C语言内置类型 但在描述复杂对象,比如“人”时,需要用到复杂类型-自定义类型(结构体,枚举,联合体)
结构体
结构体类型的声明
结构是一些值的集合,这些值成为成员变量。 结构的每个成员可以是不同类型的变量
struct tag { member - list; //成员列表 }variable - list; //变量列表(在这里创建的变量,都是结构体全局变量)
特殊声明
匿名结构体声明,只能创建全局结构体变量,由于没有类型名,main函数内无法调用。
这种方法是不成立的; 两个匿名结构体类型,各自的成员一样时,编译器在处理的时候,会当作不一样。
结构体的自引用
在结构体中包含一个类型为该结构体本身的成员是否可以?
数据域:data用来存放始数据; 指针域:next用来存放下一个数据的地址; 联系:数据结构-链表
❌
✔
结构体变量的定义和初始化
结构体内存对齐
12 8
结构体内存对齐规则
vs对齐数默认为8; gcc没有默认对其数,成员大小就是对齐数。
16
32
为什么要浪费字节
同一种结构体,同样的成员,存放排序不同,占用的内存空间不同; 怎样做到最优解呢?
第二种:就是修改默认对其数
如果设置为1,相当于没有对其数, 顺着地址往下放就行了,对其数就没有存在意义了
offsetof(); //宏 头文件:stddef.h
程序的编译里有讲
结构体传参
结论:结构体传参,要传地址;可以省多余空间
const在*p左边,const *p 修饰的就是*p; const在*右边,* const p 修饰的就是p。
结构体实现位段(位段的填充&可移植性)
int a:2; int b:5;
2,5是二进制位; 非数据,元素需定义
枚举
枚举类型的定义
枚举常量默认从0开始往下数:0 1 2 枚举常量可赋初值
枚举的优点
枚举的使用
枚举大小和一个整形的大小一致
联合(联合体、共用体)
联合类型的定义
由于 i / c 共用同一片空间,所以无法同时使用; 一改i,c也跟着该了,动c,i也跟着动。
共用同一块空间
联合的特点
这里的union也可以用匿名结构体类型
联合大小的计算
动态内存管理
当前我们知道的内存使用方式: 1.创建一个变量 int a = 10;//局部变量-栈区 int a = 10;//全局变量-静态区 2.创建一个数组 int arr[10];//局部数组-栈区 int arr[10];//全局数组-静态区
动态内存空间,是在堆区开辟一块空间
动态内存函数 头文件<stdlib.h>
malloc//开辟内存空间
效率会更高:开辟空间->直接返回地址
int *p = malloc(20); malloc的单位是字节。
free//释放内存空间
释放内存后, 指向其的指针要重置为NULL
calloc//开辟内存空间,并将其元素初始化为 0 。
效率低一点点:开辟空间->初始化元素->然后返回地址
realloc//调制动态内存开辟空间的大小
开辟10个int类型的内存块,发现不够了,用realloc再扩一些空间; 发现空间开辟的太多了,用realloc缩减一些空间。
realloc也可以用来开辟空间
结论:如果realloc第一个参数给空指针(NULL),它的功能与malloc是相似的
realloc函数使用的注意事项
1.调制函数空间时,如果p指向的空间之后有足够的内存空间可以追加,则直接追加,后放回p
2.如果p指向的空间之后没有足够的内存空间可以追加,则realloc函数会重新找一块新的内存空间,开辟一块可以满足需求的空间,并且把原来内存中的数据拷贝回来,释放旧的内存空间,最后返回新开辟的内存空间地址
3.如果realloc函数开辟失败,原指针所指向的地址会丢失,所以得用一个新的指针变量来接受realloc函数的返回值,如果没有开辟失败,则将新开辟的指针变量赋给p
常见的动态内存错误
1.对空指针的解引用操作
开辟空间失败,就会返回空指针NULL; 要是没写条件代码,解引用空指针,就裂开了
所以大多情况都会加一句: if(p == NULL) { return 0; 或者 printf("%s ",strerror(errno)); 打印错误码 }
2.对动态开辟空间的越界访问
3.对非动态开辟内存使用free函数释放
动态内存开辟在堆区! 图片中a、p是开辟在栈区!
4.使用free函数释放一块动态开辟内存的一部分
初始P被改变; free指着p说:“你已经变成了别人的形状!”
5.对同一块动态内存多次释放
如何避免: 1.谁申请谁回收 2.将p置成NULL(如图)
6.动态开辟内存忘记释放 (忘记释放,就会导致内存泄漏)
内存泄漏是毁灭性的打击
经典笔试题
仔细看,getmemory(str)不是(&str),意味着是传值,不是传址! p是getmemory的局部变量,出了get函数p就没了,且没free;又因为str从始至终都是一个NULL, “helloworld”想strcmp给str,但str是一个空指针,它无法接收字符串,所以程序崩溃。
两个问题: 1.程序崩溃 2.内存泄漏
两种正确代码:
get函数里再检测一下malloc有没有开辟空间成功; 不要不小心解引用了NULL
返回栈空间地址问题:
非法访问! getmemory函数内的 p 虽然将地址传给了str,但p本身是一个局部变量,出了get函数自动销毁。 有人给你一个地址,说房子里有“一掰完”,谁拿到是谁的,你去了发现房子被人推了,那你的地址还有意义吗? 推了之后,别人又盖了新房子,你拿着旧地址去访问别人的新房子,非法访问,私闯民宅。 别人房子里存了什么“数据”,也和你没关系。
类似问题:
如果给个 static (修饰局部变量,增加生命周期) 就没问题了
虽然ptr出了test被销毁了,但是p继承了ptr的地址,且malloc没有被free,所以没什么问题,但这种nt代码不要写
堆区开辟的空间,只要不free,一直存在。
如果有人接受x地址,将非法访问
解引用野指针
输出hello,但内存泄漏
对已释放的内存空间进行非法访问
不能乱改,人家这段代码考的就是 free 后的置 NULL 判断; 语文老师说,考题要抓住核心思想。
出自:高质量的c-c++编程指南
C/C++程序的内存开辟
数据段 = 静态区
常量放在代码段 = 常量区
OS = 操作系统
柔性数组(flexible array)
C99中,结构(结构体)中的最后一个元素允许是未知大小的数组,这就叫做【柔性数组】成员
柔性数组在使用sizeof计算成员大小时,不包含其中
打印结果:
酷壳:陈皓:C语言结构体里的成员数组和指针:https://coolshell.cn/articles/11377.html
C语言文件操作
什么是文件
磁盘中存储的文件是文件; 但是,在程序设计中,谈的文件一般有两种:程序文件、数据文件
程序文件
数据文件
程序文件 可以操作 数据文件
文件名
c:\code\ | text | .txt 文件路径 | 文件名主干 | 文件后缀
文件类型
文本文件
二进制文件
内存中的数据,直接存在外存中,就是二进制文件; 内存中的数据经过Ascii码转换,存在外存中,就是文本文件。
文件缓冲区
文件指针
文件的打开和关闭
文件的顺序读写
数据通过编译器写入文本,以二进制存放,十六进制呈现,
写入txt文档
从txt文档读出显示
文件的随机读写
文件结束的判断
程序的编译(预处理操作)+链接
程序的翻译环境
Linux环境:Vim-Gcc
.c文件经过编译器编译(翻译)生成obj目标(对象)文件,再由链接器链接生成exe可执行程序; 每个.c文件再经过编译器翻译时,都会生成独立的obj文件: add.c --> add.obj main.c --> main.obj test.c --> test.obj 源文件 -> 编译 -> 链接 -> 可执行程序 .c --> .exe 翻译环境 执行环境 编译(编译器) obj 链接(连接器) 预编译-编译-汇编
翻译环境由编译和链接组成,编译后生成obj文件,经链接器链接; 而其中的编译又分为:预编译,编译,汇编三个小部分
程序的执行环境
详解:C语言程序的编译+链接
预定义符号介绍
__FILE__//进行编译的源文件(printf("%s",__FILE__) 打印.c文件所在的路径)
__LINE__//文件当前的行号(printf("%s",__LINE__) 打印代码所在的行号)
__DATE__//文件被编译的日期(printf("%s",__DATE__) 打印代码执行时的日期)
__TIME__//文件被编译的时间(printf("%s",__TIME__) 打印代码执行时的时间)
__STDC__//如果编译器严格遵循ANSI C,其值为1,否则未定义
预处理指令 #define
注意:#define作用为替换
#define 定义标识符 语法: #define name stuff
其他预处理指令: #include #pragme #if #end if #ifdef #line
来道真题: 做不出来无所谓,往下看。
核心思路,#define定义作用替换
宏和函数的对比
注:宏是完成替换的,函数是传参,二者有着本质上的区别
#define除了可以定义符号,还可以定义宏: #define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro) 写宏的时候要注意运算符优先级,不要吝啬括号
使用gcc查看预编译代码
gcc name.c -E -> name.i
#define定义宏
带有副作用的宏参数
带有副作用的参数: 副作用就是,可以产生作用,同时会遗留下相关的属性
宏和函数对比:
函数在调用的时候,会有函数调用和返回的开销; 而宏在预处理阶段就完成了替换,没有函数的调用和返回的开销。
命定约定
预处理操作符#和##的介绍
#和##: 如何把参数插入到字符串中?
把一个宏的参数,直接替换为对应的字符串,插入字符串中
命令行定义
gcc test.c -D SZ=20
int i = 0; int arr[SZ] = {0}; for(i=0;i<SZ;i++) { arr[i] = i; printf("%d ",arr[i]); } 可以使用命令行定义来为SZ指定数值运行
/**/内的代码不必理会,是之前测试用的,看命令行定义
预处理指令#include
文件包含
为防止头文件冗余,建议在头文件中加入条件编译:
预处理指令#undef
移除一个宏定义
#undef NAME //如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。
条件编译
#define DEBUG #ifdef DEBUG//如果DEBUG被#define定义,则执行printf函数,否则在预处理时删除 printf("hello world"); #endif//结束indef
offsetof()
offsetof(结构体类型,成员名); offsetof返回指定成员从其父数据结构开始的字节偏移量。
offsetof使用
My_OFFsetof
<作业>
指针初阶
c
b
a,二级指针和一级指针都是指针变量,存储地址的,没有谁大谁小 d,32位4字节,64位8字节
c
a,整形指针+1,向后偏移一个整形 b,元素个数
A
a,arr先和 [ ] 结合,有10个元素,每个元素的类型是 int* b,指针数组创建时必须指定大小 c,二级指针 d,数组指针
拓展
1) #define是预处理指令,在编译预处理时进行简单的替换,不作正确性检查,不关含义是否正确照样带入,只有在编译已被展开的源程序时才会发现可能的错误并报错。例如: #define PI 3.1415926 程序中的:area=PI*r*r 会替换为3.1415926*r*r 如果你把#define语句中的数字9 写成字母g 预处理也照样带入。 2)typedef是在编译时处理的。它在自己的作用域内给一个已经存在的类型一个别名,但是You cannot use the typedef specifier inside a function definition。 3)typedef int * int_ptr; 与#define int_ptr int * 作用都是用int_ptr代表 int * ,但是二者不同,正如前面所说 ,#define在预处理 时进行简单的替换,而typedef不是简单替换 ,而是采用如同定义变量的方法那样来声明一种类型。也就是说; #define int_ptr int * int_ptr a, b; //相当于int * a, b; 只是简单的宏替换 typedef int* int_ptr; int_ptr a, b; //a, b 都为指向int的指针,typedef为int* 引入了一个新的助记符 这也说明了为什么下面观点成立 typedef int * pint ; #define PINT int * 那么: const pint p ;//p不可更改,但p指向的内容可更改 const PINT p ;//p可更改,但是p指向的内容不可更改。 pint是一种指针类型 const pint p 就是把指针给锁住了 p不可更改 而const PINT p 是const int * p 锁的是指针p所指的对象。 3)也许您已经注意到#define 不是语句 不要在行末加分号,否则 会连分号一块置换。 参考:http://zhidao.baidu.com/link?url=B2vywL553FqMYl9h5bMVchu4rPG15t16EfG9RNWhGC4mMaNfCHTWIuCVYStF9qDYdzRp59TcmgKmjsBhvayZ8K
<一个总论(用于初级考试)>
二个函数
scanf输入
printf输出
三种结构
顺序
选择/分支
循环/重复
四类数据
int整形
float实形/double双精度实行
char字符型
数组
五种运算符
算术
关系
逻辑
自增(减)
指针
六类表达式
算术
关系
逻辑
条件
赋值
逗号
七条语句
单分支if
双分支if
多分支if
switch
for
while
do....while
八项注意
标识符
字符常量
字符串长度
复合赋值运算
格式指示符
条件表达式
字符串内存存放
函数调用
九个定义
整型
实型
字符型
一维数组
二维数组
函数
指针
结构类中的宏定义
文件
十大经典编程
水仙花
题目:打印出1000以内的水仙花数字,一个数的各位数的^3相加等于这个数. 例:153,1^3+5^3+3^3=153
#include<stdio.h> #include<math.h> int main() { int i, j, k, n; for (n = 100; n < 1000; n++) { i = n / 100; j = (n % 100) / 10; k = n % 10; if (pow(i, 3) + pow(j, 3) + pow(k, 3) == n) { printf("%d\n",n); } } return 0; }
输出结果: 153 370 371 407
闰年
字母大小写转换
分段
最大(小)值
排序
求和
统计
数列
矩阵
计算机硬件知识<拓展>
计算机中的单位
bit//比特
一个比特只能存放一个进制数据--(如二进制的0或者1<一个比特只能放一个数字>)
byte//字节
一个字节=八个比特--(可以存放8个进制数据,如二进制一个字节的数据:01001001)
kb
1K=1024个字节
mb
1M=1024kb
gb
1G=1024mb
tb
1T=1024gb
pb
1P=1024tb
计算机进制
计算机以二进制数字储存整数,例如,整数7以二进制写是111。因此,要在8位字节中储存该数字,需要把前5位都设置成0,后3位设置成1
十进制
0 1 2 3 4 5 6 7 8 9
逢10进1
二进制//0b
0 1
逢2进1
八进制//0
0 1 2 3 4 5 6 7
逢8进1
十六进制//0x
0 1 2 3 4 5 6 7 8 9 a b c d e f
逢16进1
进制转换运算
^数学符号,次方
2^2 = 2的平方
2^3 = 2的立方
2^8 = 2的8次方
二进制转十进制
0b0101(二进制)
从右到左以此计算1*2^0+0*2^1+1*2^2+0*2^3=5(十进制)
0101的十进制是5
十进制转二进制
25(十进制)
短除法:十进制数除2,整除取0,有余取1
25的二进制是11001
由于一个字节=8个比特,所以计算机会写成 0001 1001(用0补位)
二进制转十六进制
4位二进制可以转成1位十六进制 二进制位: 0000 0000 0000 0000 0000 0000 0000 0000 十六进制位:0 0 0 0 0 0 0 0
0101 == 0x5
低位1 = 1 第二位0 = 0 第三位1 = 4 1+4 = 5
0100 0111 1011 0010 == 0x47b2
八进制转十进制
023(八进制)
3* 8^0 +2* 8^1 = 3+16 = 19
十进制数字%10/10得出十进制个位数 二进制数字%2/2得出二进制个位数
开搞:
0x 00 00 20 00的2进制是
00000000 00000000 00100000 00000000
0x00 00 20 00 + 20 =
0x00 00 20 14; 16进制,逢16进1,20-16=4,余个4
看不懂?继续 20的二进制是 0b 0001 0100 二进制转换成16进制 0x 1 4 (4位二进制可以转成1位十六进制)
源码,反码,补码
源码取反得到反码,反码+1得到补码 补码-1得到反码,反码取反得到源码
只要是整数,内存中存储的都是二进制的补码!
正数:源码,反码,补码-->相同
负数:源码符号位(首位)不变, 其他按位取反求出反码, 反码+1求出补码
图示
最高位,0正1负
直接按正负写出的二进制序列,就叫源码
ASCII转义字符表
\t--水平制表符,类似键盘Tab
\0(反斜杠零)--字符串结束标志
ASCII码