关键字
C语言的32个关键词:(由系统定义,不能重做其他定义)
1 | auto break case char const |
数据类型
数据类型大概分为俩种:基本类型数据、复合类型数据
基本类型数据
整数
1 | 整型 —— int --4字节 |
浮点数【实数】
1 | 单精度浮点数 —— float ——4字节 |
字符
1 | char ——1字节 |
复合类型数据
结构体
枚举
共用体(已淘汰)
什么是变量
变量的本质是内存中一段存储空间
变量为什么必须初始化
所谓初始化就是赋值的意思
如何定义变量
数据类型 变量名 = 要赋的值;
等价于
数据类型 变量名;
变量名 = 要赋的值;
举例子:
1 | int i = 3 ; 等价于 int i ; i = 3; |
强制类型转换
1 | int total; |
浮点数的存错所带来的问题
float和 double都不能保证可以把所有实数都准确的保存在计算机中。
1 | float i = 99.9; |
什么是进制
十进制就是逢十进一(人类最习惯使用的是十进制)
二进制就是逢二进一
生活中用到进制的例子:
一周七天: 七进制
一年十二个月: 十二进制
一小时六十分钟: 六十进制
电脑中的数据: 二进制(我们的计算机只识别二进制)
为了实际需要 我们又建立了八进制和十六进制
C语言规定八进制前要加0(注意是零不是字母o),十六进制前要加0X或0x,十进制前什么都不加!
在汇编中:在数字后加字母B表示二进制数,加字母O表示八进制数,加字母D表示十进制,加字母H表示十六进制。
例:
1 | 1011B为二进制数1011,也记为(1011)2 |
什么叫n进制
十进制
十个基数,逢10进一
基数:0 1 2 3 4 5 6 7 8 9
二进制
二个基数 逢二进一
基数: 0 1
八进制
8个基数 逢8进一
基数: 0 1 2 3 4 5 6 7
十六进制
16个基数 逢16进一
基数: 0 1 2 3 4 5 6 7 8 9 a b c d e f 或
0 1 2 3 4 5 6 7 8 9 A B C D E F
常量在c语言中如何表示的
整数
十进制: 传统的写法
十六进制: 前面加0x或0X,是数字0加x
八进制: 前面加数字0
浮点数
传统写法
float x = 3.2;//传统
科学计数法
float x = 3.2e3;//x的值是3200
float x = 123.45e-2; //x的值是1.2345
字符
单个字符用单引号括起来
‘A’表示字符A
‘AB’是错误的
“AB”是正确的
字符串用双引号括起来
“A”正确,因为”A”代表了 ‘A’ ‘\0’ 的组合
常量以什么样的二进制代码存储在计算机中
整数是以补码的形式转化为二进制代码存储在计算机中的
实数是以IEEE754标准转化为二进制代码储存在计算机中的
具体可参见末尾的 穿插在课堂中的零散知识笔记
字符的本质实际也是与整数的存储方式相同
什么是字节
字节就是存储数据的单位,并且是硬件所能访问的最小单位
1 | 1字节 = 8位 |
买硬盘时的换算是以1000为单位的
不同类型的数据之间相互赋值的问题
暂不考虑
1 | int i = 45; |
14、什么是ASCII
ASCII不是一个值,而是一种规定,ASCII规定了不同的字符是使用哪个整数值去表示,它规定了
1 | 'A' -- 65 |
字符的存储
字符本质上与整数的存储方式相同
基本的输入和输出函数
printf()
将变量的内容输出到显示器上
四种用法
1 | 1.printf ("字符串\n") |
输出控制符包含如下:
1 | %d -- int (十进制) |
为什么需要输出控制符?
- 01组成的代码可以表示数据也可以表示指令
- 如果01组成的代码表示的是数据的话,那么同样的01代码组合以不同的输出格式输出就会有不同的输出结果
scanf()
两种用法:
用法一:
1 | scanf ("输入控制符", 输入参数); |
功能:将从键盘输入的字符转化为输入控制符所规定格式的数据,然后存入已输入参数的值为地址的变量中。
用法二:
1 | scanf("非输入控制符 输入控制符", 输入参数); |
功能:将从键盘输入的字符转化为输入控制符所规定格式的数据,然后存入以输入参数的值为地址的变量中
非输入控制符必须原样输入
如何使用scanf编写出高质量代码
使用scanf之前最好先使用printf提示用户以什么样的方式来输入
Scanf中尽量不要使用非输入控制符,尤其是不要用\n
应该编写代码对用户的非法输入做适当的处理【非重点】
1
2while ((ch=getchar()) != '\n')
continue;运算符
算术运算符
关系运算符
逻辑运算符
位运算符
赋值运算符
杂项运算符
算术运算符
五个关键符号:+ - * /(除) %(取余数)
- 除法的运算结果和运算对象的数据类型有关,两个数都是int,则商就是int,若商有小数,则截取小数部分;
- 被除数和除数中只要有一个或两个都是浮点型数据,则商也是浮点型,不截取小数部分。
- 取余的运算对象必须是整数,结果是整除后的余数,其余数的符号与被除数相等。
如:
1 | 16/5 == 3 |
关系运算符
六个符号
1 | >(大于) >=(大于等于) <(小于) <=(小于等于) !=(不等于) ==(等于) |
逻辑运算法
三个关键符号:!(非) &&(并且) ||(或)
1 | !真 假 |
C语言对真假的处理
非零是真
零是假
真用1表示
假用0表示
&&左边的表达式为假 右边的表达式肯定不会执行
| |左边的表达式为真 右边的表达式肯定不会执行
赋值运算符
五个符号= += *= /= -=
位运算符
&(按位于)、| (按位或)、~(按位取反)、^(按位异或)、<<(按位左移)、>>(按位右移)
按位于
1 | 1&1 = 1 |
&& 逻辑与 也叫并且,注意&&与& 的含义完全不同
按位或
1 | 1|0 = 1 |
|| 逻辑或与| 按位或含义不同
按位异或
相同为零不同为1
1 | 1^0 = 1 |
按位左移
i<<3表示把i的所有二进制位左移3位,右边补零;左移n位相当于乘以2的n次方
1 | A)i = i*8 |
请问上述两个语句,哪个语句执行的速度快。
答案: B快
按位右移
I>>3表示把i的所有二进制位右移3位,左边一般是0,当然也可能补1。
右移n位相当于除以2的n次方,前提是数据不能丢失。
1 | A)i = i/8 |
请问上述两个语句,哪个语句执行的速度快。
答案: B快
位运算的现实意义
通过位运算符我们可以对数据的操作精确到每一位
优先级别
优先级:算术运算符 > 关系运算符 > 逻辑运算符 > 赋值运算符。逻辑运算符中“逻辑非 !”除外
还有一些自增 自减 三目运算符 逗号表达式在这就不过多阐述了
琐碎的运算符知识
自增
自增【或者自减】
前自增 – ++i
后自增 – i++
相同: 最终都使i的值加1
不同:前自增整体表达式的值是i加1之后的值;后自增整体表达式的值是i加1之前的值。区别在于一个后加,一个先加
自增的好处:代码更精练;自增的速度更快。
三目运算符
1 | A ? B : C |
逗号表达式
格式:(A, B, C, D)
功能:从左到右执行;最终表达式的值是最后一项的值
流程控制
流程控制:程序代码执行的顺序。
流程控制的分类有俩种:
顺序
根据程序执行文本自上至下执行
选择
定义:某些代码可执行,也可能不执行,有选择的执行某些代码
if最简单的用法
格式:1
2if (表达式)
语句解释:如果表达式为真,执行语句,如果表达式为假,语句不执行
if的范围问题(重点)
1
2
3if (表达式)
语句A;
语句B;解释:if默认只能控制语句A的执行或不执行,if无法控制语句B的执行或不执行;或者讲: 语句B一定会执行
域范围
1
2
3
4
5if (表达式)
{
语句A;
语句B;
}如果表达式成立则语句A和语句B均会执行。
由此可见:if默认只能控制一个语句的执行或不执行,如果想控制多个语句的执行或不执行就必须把这些语句用{}括起来。
if…else if的用法
格式:
1 | if (表达式1) |
如果表达式1不成立则判断表达式2(若成立则执行语句B),若不成立则判断表达式3,如果全部都不成立,则执行D语句
如何看懂一个程序
分三步:
- 流程
- 每个语句的功能
- 试 数
如何学习一些需要算法的程序
- 尝试自己去编程解决它但要意识到大部分人都是自己无法解决的,如果解决不了,这时不要气馁,如果十五分钟还想不出来,此时我建议您就可以看答案了
- 如果解决不了,就看答案,关键是把答案看懂,这个要花很大的精力,也是我们学习的重点,看懂一个程序要分三步:流程、每个语句的功能、试数
- 看懂之后尝试自己去修改程序,并且知道修改之后程序的不同输出结果的含义不建议看懂程序之后就立即自己敲程序
- 照着答案去敲
- 调试错误
- 不看答案,自己独立把答案敲出来
- 如果程序实在无法彻底理解,就把它背会,无法彻底理解的程序很少很少几乎没有
循环
定义:某些代码会被重复执行。
- for
- while
- do…while
for
1 | for(1; 2; 3) //1 |
整体式两个语句,1 2 3是一个语句4是第二个语句。
while
for和while可以相互转换
1 | for(1; 2; 3) |
while和for可以相互转化,但for的逻辑性更强,更不容易出错,推荐多使用for。什么时候使用while,什么时候使用for
没法说,用多了自然而然就知道了。
do…while
1 | do{ |
无论while括号成立与否都会执行语句A。
关键字break和continue:
break(很重要)
break如果用于循环是用来终止循环
break如果用于switch,则是用于终止switch
break不能直接用于if,除非if属于循环内部的
在多层循环中,break只能终止距离它最近的那个循环
1 | for (i=0; i<3; ++i) |
continue(用的很少)
用于跳过本次循环余下的语句,转去判断是否
1 | for (1; 2; 3) |
数组
1.为什么需要数组?
为了解决大量同类型数据的存储和使用问题。
为了模拟现实世界
c语言不像其他语言存在对象(jvascript(let object=new Object()),java),所以需要数组去模拟。
2.数组的分类
一维数组、二维数组、多维数组。
一维数组
怎么定义一维数组?
为n个变量连续分配存储空间
所有的变量数据类型必须相同
所有变量所占的字节大小必须相等
例子:
1 | int a[5]; //(如果是js则var a[5];则会报错,声明必须赋值) |
一维数组名不代表数组中所有的元素,
一维数组名代表数组第一个元素的地址
有关一维数组的操作
初始化
1 | int a[5] = {1,2,3,4,5}; //完全初始化 |
二维数组
1 | int a[3][4]; //总共是12元素看,可以当做3行四列看待 |
这12个元素的名字依次是:
1 | a[0][0] a[0][1] a[0][2] a[0][3] |
初始化俩种方式
1 | int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12}; |
输出二维数组的内容
1 | for (i=0; i<3; ++i){ |
多维数组
是否存在多维数组?
不存在,因为内存是线性唯一的,n维数组可以当做每一个元素是n-1维数组的一维数组,比如:int a[3][4];
,该数组是含有3个元素的一维数组,只不过每一个元素都是4行5列的二维数
函数
为什么需要函数?
- 避免了重复性操作
- 有利于程序的模块化
什么叫做函数?
- 逻辑上:能够完成特定功能的独立的代码单元
- 物理上:能够接受数据【当然也可以不接受数据】
- 能够对接收数据进行处理
- 能够将数据处理的结果返回【当然也可以不返回任何值 前面定义成void】
总结: 函数是个工具,它是为了解决大量类似问题而设计的,函数可以当做一个黑匣子
如何定义函数?
格式:
1 | 函数的返回值 函数的名字(函数的形参列表) |
函数返回值的类型也称为函数的类型,因为如果函数名前的返回值的类型和函数执行体中的return表达式;中表达式的类型不同的话,则最终函数返回值的类型 以函数名前的返回值类型为准。
1 | int f() //只能返回整数类型 |
函数的分类
- 有参函数 和 无参函数
- 有返回值函数 和 无返回值函数
- 库函数 和 用户自定义函数
- 值传递函数 和 地址传递函数
- 普通函数 和 主函数(main函数)
一个程序必须有且只能有一个主函数;主函数可以调用普通函数,普通函数不能调用主函数,普通函数可以相互调用;主函数是程序的入口,也是程序的出口
注意的问题
函数调用和函数定义的顺序
如果函数调用写在了函数定义的前面,则必须加函数前置声明
函数前置声明:
- 告诉编译器即将可能出现的若干个字母代表的是一个函数
- 告诉编译器即将可能出现的若干个字母所代表的函数的形参和返回值的具体情况
- 函数声明是一个语句,末尾必须加分号
- 对库函数的声明是通过 #include <库函数所在的文件的名字.h>来实现
形参和实参: 个数相同 位置一一对应 数据类型必须相互兼容
一个函数的功能尽量独立,单一;多学习, 多模仿牛人的代码;函数是C语言的基本单位,类是Java,C#,C++的基本单位
变量的作用域和存储方式
按作用域分:
全局变量
在所有函数外部定义的变量叫全局变量.
全局变量使用范围: 从定义位置开始到整个程序结束
局部变量
在一个函数内部定义的变量或者函数的形参都统称为局部变量,而且只能在本函数内部使用
1 | void f (int i) |
注意的问题:
全局变量和局部变量命名冲突的问题,在一个函数内部如果定义的局部变量的名字和全局变量名一样时,局部变量会屏蔽掉全局变量。
按变量的存储方式
- 静态变量
- 自动变量
- 寄存器变量
指针
指针是c语言的灵魂
指针的重要性
- 表示一些复杂的数据结构
- 快速传递数据,减少了内存的耗用【重点】
- 使函数返回一个以上的值【重点】
- 能直接访问硬件
- 能够方便的处理字符串
- 是理解面向对象语言中引用的基础
指针的定义
指针的定义
地址:是内存单元的编号。
1 | 指针就是地址,地址就是指针 |
但是要注意:通常我们叙述时会把指针变量简称为指针,实际他们含义并不一样,指针的本质就是一个操作受限的非负整数。
指针的分类
1.基本类型指针
1 | int * p; //p是变量的名字,int * 表示p变量存放的int类型变量的地址 |
所谓int *
类型 实际就是存放int变量地址的类型
1 | int i = 3; |
- 上面p保存了i的地址,因此p指向i
- p不是i,i也不是p,更准确的说:修改p的值不影响i的值,修改i的值也不会影响p的值
- 如果一个指针变量指向了某个普通变量,则*指针变量就完全等同于普通变量
例子:
如果p是个指针变量。并且p存放了普通变量i的地址,则p指向了普通变量i,p就完全等同于i;或者说:在所有出现p的地方都可以替换成i,在所有出现i的地方都可以替换成*p。
*p 就是以p的内容为地址的变量
经典指针程序_互换两个数
1 | //如果要互换p和q的值,则t必须是int *,不能是int,否则会出错 |
*的含义
- 1.乘法
- 2.定义指针变量
- 3.int * p; //定义了一个名字叫p的变量,int *表示只能存放int变量的地址指针运算符
该运算符放在已经定义好的指针变量的前面,如果p是一个已经定义好的指针变量,则 *p表示 以p的内容为地址的变量。
如何通过被调函数修改主调函数普通变量的值
- 1.实参必须为该普通变量的地址
- 2.形参必须为指针变量
- 3.在被调函数中通过*形参名 = ……的方式就可以修改主调函数相关变量的值
指针和数组
指针和一维数组
一维数组名是个指针常量它存放的是一维数组第一个元素的地址
下标和指针如果p是个指针变量,则p[i]永远等价于 *p(p+i)
确定一个一维数组需要几个参数?
如果一个函数要处理一个一维数组,则需要接收该数组的哪些信息
需要两个参数:
- 数组第一个元素的地址
- 数组的长度
注:指针变量不能相加,不能相乘,也不能相除,如果两个指针变量指向的是同一块连续空间中的不同存储单元,则这两个指针变量才可以相减。
一个指针变量到底占几个字节?【非重点】sizeof(数据类型)
功能:返回值就是该数据类型所占的字节数
例子:
1 | sizeof(int) = 4; |
功能:返回值是该变量所占的字节数
- 假设p指向char类型变量(1个字节)
- 假设q指向int类型变量(4个字节)
- 假设r指向double类型变量(8个字节)
p q r 本身所占的字节数是否一样?
答案: p q r 本身所占字节数是一样的
总结: 一个指针变量,无论它指向的变量占几个字节,该指针变量本身只占四个字节,一个变量的地址是用该变量首字节的地址来表示
动态内存分配
传统数组的缺点:
1.数组长度必须事现指定,且只能是常整数,不能是变量
例子:1
2int a [5]; //OK
int len = 5; int a[len]; //error2.传统形式定义的数组,该数组的内存程序员无法手动释放
数组一旦定义,系统就为该数组分配的存储空间就会一直存在,除非数组所在的函数运行结束;或者说:在一个函数运行期间,系统为该函数中数组所分配的空间会一直存在,直到该函数运行完毕时,数组的空间才会被系统释放。3.数组的长度不能在函数运行的过程中动态的扩充或缩小
数组的长度一旦定义,其长度就不能再更改4.传统方式定义的数组不能跨函数使用
A函数定义的数组,在A函数运行期间可以被其它函数使用,但A函数运行完毕之后,A函数中的数组将无法再被其他函数使用
为什么需要动态内存分配内存
动态数组很好的解决了传统数组的这4个缺陷,传统数组也叫静态数组。
静态内存和动态内存的比较
- 静态内存是有系统自动分配,由系统自动释放
- 静态内存是在栈中分配的
- 动态内存是由程序员手动分配,手动释放
- 动态内存是在堆中分配的
- 跨函数使用内存的问题
malloc()
malloc(), calloc(), realloc() ,这三个函数都是向堆中申请的内存空间.这里只讲malloc()
- 1).malloc()函数会向堆中申请一片连续的可用内存空间
- 2).若申请成功 ,,返回指向这片内存空间的指针 ,若失败 ,则会返回NULL, 所以我们在用malloc()函数开辟动态内存- 之后, 一定要判断函数返回值是否为NULL.
- 3).返回值的类型为void * 型, malloc()函数并不知道连续开辟的size个字节是存储什么类型数据的 ,所以需要我们自行决定 ,方法是在malloc()前加强制转 ,转化成我们所需类型 ,如: (int*)malloc(sizeof(int)*n).
- 4).如果size为0, 此行为是未定义的, 会发生未知错误, 取决于编译器
例子:相当于创建了一个数组 p[n] ,这个n的值并不需要像定义一个普通数组一样必须是常量, 可以使程序运行时得出的, 或是用户输入的1
2
3
4
5
6
7int *p = NULL;
int n = 0;
scanf("%d", &n); //由用户输入获得内存分配
p = (int*)malloc(sizeof(int) * n);
if(p != NULL){
//....需要进行的操作
}
内存操作函数还有calloc()、realloc()、free(),这里就不做过多阐述了,自行搜索C语言动态内存分配函数
1).malloc()和calloc()函数用法一样, 唯一的区别是calloc()会对所申请内存的每个字节初始化为0
2).malloc(), calloc(), realloc()申请的内存不再使用时 ,一定要用free()释放 ,否则会造成内存泄漏
3).p = realloc(ptr, size)函数返回值不为空时, 释放内存时不需写free(ptr) ,只需写free(p)
结构体
为什么需要结构体?
为了表示一些复杂的事物,而普通的基本类型无法满足实际要求。
什么叫结构体?
把一些基本类型数据组合在一起形成的一个新的复合数据类型,这个叫做结构体。
如何定义结构体
3种方式,推荐使用第一种:
1 | //第一种方式 |
怎么使用结构体变量
- 赋值和初始化
- 定义的同时可以整体赋初值
- 如果定义完之后,则只能单个的赋初值
如何取出结构体变量中的每一个成员【重点】
1.结构体变量名.成员名
2.指针变量名—>成员名 (第二种方式更常用)
指针变量名—>成员名 在计算机内部会被转化成 (*指针变量名).成员名的方式来执行
所以说这两种方式是等价的
1 | struct Student |
- pst—>在计算机内部会被转换成 (*pst).age, 没有什么为什么,这就是—>的含义,这也是一种硬性规定
- 所以 pst—>age等价于(*pst).age也等价于st.age
- 我们之所以知道pst—>age等价于st.age,是因为pst—>age是被转化成了(*pst).age来执行
- pst—>age 的含义:pst所指向的那个结构体变量中的age这个成员
结构体变量和结构体变量指针作为函数参数传递的问题
推荐使用结构体指针变量作为函数参数来传递
结构体变量不能相加,不能相减,也不能互相乘除,但结构体变量可以相互赋值
1 | 例子: |
枚举
什么是枚举?
把一个事物所有可能的取值一一列举出来
枚举的优缺点
- 代码更安全
- 书写麻烦
应用
1 | /*红色、黄色、绿色、蓝色,黑色*/ |
专题:
一、有符号数的编码规则
原码:最高位为符号位,其余各位为数值本身的绝对值。
反码:正数的反码与原码相同;负数的反码,符号位为1,其余位对原码取反。
补码:正数的补码与原码相同;负数的补码,符号位为1,其余位对原码取反加1
比如1字节数据(8位)
1 | 1的原码为 00000001 (0x01) |
原码
也叫 符号-绝对值码
最高位0表示正 1表示负,其余二进制位是该数字的绝对值的二进制位
特性
原码简单易懂
加减运算复杂
存在加减乘除四种运算,增加了cpu的复杂度
零的表示不唯一
求原码:X≥0,则符号位为0,其余照抄;X≤0,则符号位为1,其余照抄
1 | 【例1】X=+1001001 [X]原 = 01001001 |
反码
反码运算不便,也没有在计算机中应用
补码
原码取反加一
进制转换
已知十进制求二进制
正整数转二进制
除2取余,直至商为零,余数倒序排序
1 |
|
负整数转二进制
先求与该负数线对应的正整数的补码,然后将所有位取反,末尾加1,不够位数时,左边补1
假设有一个 int 类型的数,值为5,那么,我们知道它在计算机中表示为:
00000000 00000000 00000000 00000101
5转换成二制是101,不过int类型的数占用4字节(32位),所以前面填了一堆0。
现在想知道,-5在计算机中如何表示?
答: 在计算机中,负数以原码的补码形式表达。
零转二进制
全是零
二进制转XX
二进制求十进制
如果首位是0,则表明是正整数,按普通方法来求
如果首位是1,则表明是负整数,将所有位取反,末尾加1,所得数字就是该负数的绝对值
1 |
|
二进制转换十六进制
C语言中,二进制的4位数据表示1位的十六进制数字 如:10010101 ——> 1001 0101——>0x95
十进制转十六进制
1 |
|
十六进制转字符串
1 |
|
算法概论
通俗定义
解题的方法和步骤
狭义定义
对存储数据的操作
对不同的存储结果,要完成某一个功能所执行的操作是不一样的。
比如:要输出数组中所有的元素的操作和要输出链表中所有元素的操作肯定是不一样的
这说明:算法是依附于存储结构的不同的存储结构,所执行的算法是不一样的
广义定义
广义的算法也叫泛型,无论数据是如何存储的,对数据的操作都是一样的
我们至少可以通过两种结构来存储数据
数组
优点
:存取速度快缺点
:需要一个连续的很大是内存,插入和删除元素的效率很低
链表
优点
:插入删除元素效率高,不需要一个连续的很大的内存缺点
:查找某个位置的元素效率低
专业术语:
1.头结点
头结点的数据类型和首结点的类型一模一样头结点是首结点前面的那个结点,头结点并不存放有效数据,设置头结点的目的是为了方便对链表的操作。
2.头指针
存放头结点地址的指针变量
3.首节点
存放第一个有效数据的结点
4.尾节点
存放最后一个有效数据的结点
确定一个链表需要一个参数头指针