C语言笔记
标识符
用来标识数据类型,变量,函数,语句的符号
类别 | 关键字 |
---|---|
数据类型关键字 | char,double,enum,float,int,long,short,signed,struct,union,unsigned,void |
控制语句关键字 | break,continue,case,do,default,else,for,goto,if,return,switch,while |
存储类型关键字 | auto,extern,register,static |
其他关键字 | const,sizeof,typedef,volatile |
数据类型
常量和变量
- 常量:值一直不变的量
- 变量:值可以变化的量
变量
定义格式
1 | 类型说明符 变量名; |
eg:
1 | int variable; //定义一个整形变量 |
整形变量
short int | 短整型 | 2 |
---|---|---|
int | 整型 | 4 |
long int | 长整型 | 4 |
unsigned short | 无符号短整型 | 2 |
unsigned int | 无符号整型 | 4 |
unsigned long | 无符号长整型 | 4 |
- 无符号整数占用的字节数与相应的有符号整数相同。但是不能表示负数。
- 有符号整数以二进制补码形式存储,最左边第一位是符号位。0正1负
- 无符号整数以二进制原码形式存储。
- 可以和unsigned组合,定义无符号整数,打印时占位符用%u
eg:
1 | #include<stdio.h> |
浮点型变量
float | 单精度 |
---|---|
double | 双精度 |
1 | #include<stdio.h> |
字符型变量
关键词char
eg:
1 | #include<stdio.h> |
常量
(程序在执行过程中,值不发生改变的 量。)
整型常量
- 包括,正整数,0,负整数(这个很重要,容易把0忘了)。
浮点型常量
- 浮点型数,小数点是浮动的。
字符常量
- 字符型常量指的是单个字符,用一对单引号表示。每个字符在内存中占用一个字节。
- C语言中的字符常量具有数值特征,可以像整数一样参加运算,此时相当于对字符的ASCII码的运 算。
字符串常量
- 字符串常量是由一对双引号括起来的零个或者多个字符序列。
- 字符串常量在内存中占用一段连续的存储单元,系统在每个字符串的尾部加上’\0’作为字符从的结 束标志
- 因此n个字符组成的字符串,在内存中要占用n+1个字节空间。
- 可以使用sizeof运算符来计算字符串占用的字节数。
eg:
1 | #include<stdio.h> |
宏定义(符号常量)
1 | #define 标识符 常量值 |
变量的定义和使用
所以变量都要先定义再初始化
先定义再初始化
1 | int a,r; |
定义的同时初始化
1 | int x = 100; |
变量的使用例子
1 |
|
运算符和表达式
赋值运算符
赋值运算符的左侧必须是个变量
运算符 | 含义 | 例子 |
---|---|---|
+= | 加法赋值 | x += 5; // 等价于 x = x + 5; |
-= | 减法赋值 | x -= 5; // 等价于 x = x - 5; |
*= | 乘法赋值 | x *= 5; // 等价于 x = x * 5; |
/= | 除法赋值 | x /= 5; // 等价于 x = x / 5; |
<<= | 取模赋值 | x %= 5; // 等价于 x = x % 5; |
>>= | 左移位赋值 | x <<= 5; // 等价于 x = x << 5; |
&= | 按位与赋值 | x &= 5; // 等价于 x = x & 5; |
^= | 按位异或赋值 | x ^= 5; // 等价于 x = x ^ 5; |
|= | 按位或赋值 | x |=5; //等价于x = x | 5; |
算数运算符
运算符 | 含义 |
---|---|
+ | 加 |
- | 减 |
= | 等于 |
/ | 除 |
% | 取余 |
自增运算符
运算符 | 含义 |
---|---|
++x; | 加号在前,先把x的值+1,再把加了之后的值给变量a |
x++; | 加号在后,先把x的值赋值给变量a,再把x的值+1 |
–x; | 减号在前,先把x的值-1,再把减了之后的值给变量a |
x–; | 减在后,先把x的值赋值给变量a,再把x的值-1 |
sizeof
求字节数
eg:
1 | #include<stdio.h> |
数据类型转换
- 不同的数据类型放在一块一起运算,需要转换成相同的类型才能进行运算。
- (中国人和外国人,之间交流,也需要转换,总要有一个人,要学中文,或者一个人学英文)
自动转换
系统按照默认的规则转换。
强制转换
1 | 格式:(类型说明符) (表达式); |
eg:
1 | #include "stdio.h" |
运算符的优先级
进制
进制也就是进位计数制,是人为定义的计数方法。 对于任何一种进制—X进制,就表示每一位上的数运 算时都是逢X进一位。 十进制是逢十进一,十六进制是逢十六进一,二进制就是逢二进一,以此类推,x进制就是逢x进位。
位
计数单位是,位或者比特(bit),简写,小写b,每个位只能存放1个二进制位。
字节
Byte,字节。1个字节=8位。除此之外,还有KB,MB,GB,TB,PB,EB。1KB=1024B=2的10次方B。
R进制转换为10进制
点,为分隔符。例如10进制。326.56。
1 | 326.56 = 3*10^2 + 2*10^1 + 6*10^0 + 5*10^-1 + 6*10^-2 |
例如8进制,165.2
1 | 165.2 = 1*8^2 + 6*8^1 + 5*8^0 + 2*8^-1 |
例如8进制。165.2
1 | 165.2 = 1*8^2 + 6*8^1 + 5*8^0 + 2*8^-1 |
例如2进制。1011.1。
1 | 1011.1 = 1*2^3 + 0*2^2 + 1*2^1 + 1*2^0 + 1*2^-1 |
通过这种规律,可以将所有其他进制转换为10进制。
10进制转R进制
十进制有整数部分和小数部分,需要分开转换。整数部分:除R取余。
小数部分:乘R取整。
2进制<–>8进制
以小数点为基准,向左右两边划分,每三位一组,不够的补0
2进制<–>16进制
有符号无符号数
计算机中规定:二进制数的最高位是符号位。0,表示该数是正数,1,表示该数为负数。
1 | 所以,正数50就是 00110010 负数50就是 10110010 |
定点整数和定点小数
010Editor
数据在电脑中是如何储存的可以通过010 Editor来观察
原码反码补码
所有的0和1代码,在电路中表现,都是加减的操作。按照常规思路,应该是下面这种。
1 | +40 00101000 |
计算机中存放的数,是以补码存在和运算的。
原码
一个二进制数同时包含符号和数值两个部分,最高位是符号位。
反码
- 正数,的原码和反码相同。
- 负数,其反码,除了符号位不变外,其余各位,0换成1,1换成0
补码
- 正数的,补码和原码相同。
- 负数的,补码=反码+1。
顺序结构程序设计
顺序式样
输出printf()
1 | #include<stdio.h> |
输入scanf()
1 | #include<stdio.h> |
scanf和printf,这两个函数格式(你希望以什么样的形式输入,希望以什么样的形式输出)
1 | printf("格式控制字符串",输出列表项); |
高频使用格式整理
%d | 十进制(int) |
---|---|
%o | 八进制(int) |
%x,%X | 十六进制(int) |
%u | 十进制(无符号)(int) |
%f | ( float 和 double ) |
%l | 输出长整型 |
%m | 输出指定宽度。如果值>m,输出实际。否则,空格补全 |
%c | 单个字符 |
1 | #include "stdio.h" |
字符数据输入和输出
用于单个字符。getchar和putchar。
1 | #include<stdio.h> |
数学标准函数
有些函数需要引入,math.h,有些引入标准库即可。
函数名 | 含义 |
---|---|
abs() | 求绝对值 |
acos() | 求反余弦 |
asin() | 求反正弦 |
atan() | 求反正切 |
atan2() | 求反正切,按符号判定象限 |
ceil() | 求不小于某值的最小正数。 |
cos() | 求余弦 |
fabs() | 求浮点数的绝对值 |
floor() | 求不大于某值的最大整数 |
1 | #include "stdio.h" |
循环结构
for循环
1 | 格式 |
情况1:
1 | for(;表达式2;表达式3) |
情况2
1 | for(表达式1;;表达式3) |
情况3
1 | for(表达式1;表达式2;) |
- for循环中的,表达式1和表达式3也可以是逗号表达式(建议少用)
- 只要表达式2的值非0,就执行循环体
- 当然,还有其他的变形,但是不建议使用变形(会基本结构和上面三种变形即可)
while语句
1 | 格式 |
- 计算表达式的值,如果为真,向下执行,如果为假,执行while语句块下面的
- 执行循环体
- 返回第一步
- 结束循环,执行while语句块下面的语句
- 先判断循环条件,再执行循环体。
1 | // 计算100以内的所有数的和 |
do-while语句
1 | 格式 |
1 | #include "stdio.h" |
- while循环和do while循环,有不太一样的地方
- 先执行循环体语句 计算表达式的值,如果为真,返回到第一步,如果为假,结束循环,执行语句块下面的内容
- do后面没有分号,while(表达式)后面有分号。
- 循环体中必须有改变循环条件的语句,否则会出现死循环
break和continue
- break:用于循环语句中,可使程序终止循环,而转去执行循环语句的后继语句。
- continue:跳过循环体中,continue后面的语句,继续下一次循环。
数组
数组是相同数据类型的,有序数据的集合。(有序数据指的是数据中的每一个数据都是有序号的,按顺 序排序的)
- 如论如何记住一点:数组名是数组首个元素的地址。
1 维数组定义
1 | 格式 |
同一数组的数组元素在内存中用的是一片连续的存储单元。(和内存有关系)
1 | int a[8]; #定义一个有8个整型元素的数组a |
1 维数组初始化
定义的同时初始化(初始状态)
1
2
3
4int a[6]={1,2,3,4,5,6}; // 规范
// 等价于
int a[]={1,2,3,4,5,6};只给几个赋值,其他的都是0。
1
int a[6]={1,2,3};
一个数组中所有的元素都是0,可以简写
1
int a[6]={0};
相关demo
1 | #include "stdio.h" |
2维数组定义
存储4名学生3门课程的成绩。
1
int score[4][3];
表示方式
1
2
3
4
5int score[4][3]; #4行3列
# 从score[0][0]到score[3][2],分别代表行和列
# 列,从第0列到第2列
# 行,从第0行到第3行
# 下标
多维数组
1 | score[4][3]可以看成是 |
二位数组(包含更多维数组)在内存中是按行存放的。在内存中先存放第1行,再存放第2行,以此类推
1
score[1][1]=5; # 二维数组元素的赋值和引用
2 维数组的初始化
按行赋值,这种方式比较直观
1
2
3
4
5int a[3][4]={
{1,2,3,4},
{5,6,7,8},
{9,10,11,12}
};所有数据都放到一个大括号里面,按数组元素排列的顺序赋值
1
int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
部分赋值,其他没有赋值的元素初始值为0
1
2
3
4
5int a[3][4]={
{1},
{5,6},
{0,0,11}
};行可以省略,但是列不能省略
1
2
3int b[2][3]={1,2,3,4,5,6}
int b[][3]={1,2,3,4,5,6}
scanf补充
1 | #include<stdio.h> |
也就是说,黑窗口是可以输入内容的,但是输入的内容,有没有被程序读取到,那就的看scanf的顺序了
加了循环,能多读取几个。不加循环,那就读取一个。。
字符组成的数组
字符数组定义
- 和整型数组一样,也分为一维字符数组和二维字符数组。
- (由一个一个的字符组成的数组)
一维字符数组
1 | char str[5]; #定义了一个字符数组,可以存储5个字符型数据 |
二维字符数组
1 | char book[5][20]; #定义一个二维数组,用来存放5本书的书名 |
字符数组的初始化
用数组的方式来存放字符串,数组里面的每一个值,都是一个字符。
方式1:直接赋值。
1 | char str[5]={'c','h','i','n','a'}; |
1 | # 打印字符数组里面的所有字符 |
方式2:允许使用字符串常量对字符数组进行初始化。(这个比较方便,也比较常见)
1 | #include "stdio.h" |
字符数组在定义时,如果没有初始化,那么元素的初值是不确定的。
二维字符数组
1 | #include "stdio.h" |
字符串
内存中存储字符串时,除了存储有效字符以外,还需要在后面有一个’\0’。作为字符串结束的标志。
通常是利用字符数组来存放字符串。(字符串是通过字符数组来存储和处理的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17char str[6]={"hello"}; # (这种次常见)
# 用字符数组存放 的 字符串
# 等价于
char str[6]="hello"; # (这种最为常见)
# 用字符数组存放 的 字符串
# 等价于
char str[6]={'c','h','i','n','a','\0'}; # 用字符数组存放的字符串
# 用字符数组存放的字符串
# ===============上面3种初始化的方式,都是等价的========================
# 这三种方式,无论如何都要记住
# 这三种方式,无论如何都要记住
# 这三种方式,无论如何都要记住
# 但是,和下面这个不等价,因为没有结束标志,所以,只是一个字符数组,而不是一个字符串。
# 但是,和下面这个不等价,因为没有结束标志,所以,只是一个字符数组,而不是一个字符串。
# 但是,和下面这个不等价,因为没有结束标志,所以,只是一个字符数组,而不是一个字符串。
char str[]={'c','h','i','n','a'};
# 只是一个字符数组
一定要注意:
- 字符串是用字符数组存储的(在内存中)
- 但是字符数组不一定就是字符串。但是字符串是通过字符数组存放的。
- 因为字符串一定要有一个结束标志。
- 所以,存储字符串的数组长度,至少是字符串的有效长度+1。
- 不要忘了前面讲过的,单引号和双引号的使用,在什么情况下使用
- 单引号和双引号是重要的区分。
- 想不起来字符串和字符数组的时候,就想想单引号和双引号。
1 | #include "stdio.h" |
字符串的整体输入/输出
方式1:整体
1 | #include "stdio.h" |
方式2:gets输入
只能输入一个字符串,遇到回车结束。
1 | #include "stdio.h" |
方式3:puts输出
只能输出一个字符串,输出后自动换行。
1 | #include "stdio.h" |
缓冲区溢出(堆栈)
先在这个地方做个标记(gets是一个危险函数可能会缓冲区溢出)
1 | #include "stdio.h" |
第一行是输入,溢出了,覆盖了变量b
字符串处理函数
要用到的标准库
1 | #include <string.h> |
strcpy()
1 | # strcpy(des,src) |
- 功能:将字符串2复制到字符串1(相当于替换的效果)
- 字符串2可以是字符数组名,也可以是字符串常量
- 字符串1必须有足够的空间
- 复制时遇到字符串2的结束符为止
- 覆盖的效果
1 | #include "stdio.h" |
strncpy()
- 将字符串2的前n个字符复制到字符串1中。
- 其实就是部分替换的效
1 | # strncpy(字符串1,字符串2,n) |
strcat()
- 拼接两个字符串
- 连接时,字符串2的首字符,会覆盖字符串1的结束符’\0’
1 | //strcat(字符串1,字符串2) |
strcmp()
字符串比较函数
1
2
3
4
5
6
7
8
9
10
11
12
13// strcmp(字符串1,字符串2)
#include "stdio.h"
#include <string.h>
int main() {
char str1[30]="qwer5";
char str2[30]="qwert";
int result;
result = strcmp(str1,str2);
printf("%d",result);
return 666;
}
返回值为一个整数
- 字符串1<字符串2,返回值<0,一般是,-1
- 字符串1=字符串2,返回值=0。
- 字符串1>字符串2,返回值>0,一般是,1。
比较规则
- 从字符串的首字符开始,依次比较对应字符串的ASCII码,直到出现不同的字符或者结束符为止。
- 以第一个不相同字符串的比较结果为准,返回两个字符的差。
strlen()
计算字符串的长度,但不包括结束标志 \0。
1
2
3
4
5
6
7
8
9
10// strlen(字符串)
#include "stdio.h"
#include "string.h"
int main() {
char str1[30]="qwert";
int x = strlen(str1);
printf("%d", x);
return 666;
}
strlwr()
大写转小写。
1
2
3
4
5
6
7
8
9
10// strlwr(字符串)
#include "stdio.h"
#include <string.h>
int main() {
char str1[30]="QWERT";
puts(strlwr(str1));
return 666;
}
strupr()
- 小写转大写
1 | # strupr(字符串) |
习惯
- 如果是字符数组,一般命名里面包含char。
- 如果是字符串,一般命名里面包含str。
- 这样命名的好处是,方便自己分辨清楚。
函数
分类
- 标准函数:(本地库函数)C语言已经提供了大量已经设计好的函数,可以直接调用
- 自定义函数:用户根据自己的需求,定义的函数
- 第三方函数:除了上述两种函数之外的函数,都叫第三方函数
自定义函数的格式
无参数无返回值
1 | 格式 |
demo
1 | #include<stdio.h> |
无参数有返回值
demo
1 | #include<stdio.h> |
有参数无返回值
1 | 类型名 函数名(类型1 形参1,类型2 形参2,类型3 形参3,...类型n 形参n){ |
demo
1 | #include "stdio.h" |
有参数有返回值
demo
1 | #include "stdio.h" |
三种函数的调用方式
- 调用一个没有返回值的函数:只是要求函数完成一定的操作
- 以函数返回值参与表达式的运算。a=abs(x)
- 函数的嵌套调用。putchar(getchar())
指针
指针也就是内存地址,指针变量是用来存放内存地址的变量。就像其他变量或常量一样,在使用指针存储其他变量地址之前,对其进行声明。
指针的访问方式
直接访问
demo
1 | #include"stdio.h" |
- 这种利用变量名,访问分配给变量的内存空间的方式,称为直接访问。
间接访问
1 | #include"stdio.h" |
- 第一步:从变量p中取出变量num的首地址
- 第二步:根据取到的变量a的首地址,再访问变量num。
指针变量的初始化
指向变量的指针的初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
201.定义后初始化
#include"stdio.h"
int main(){
int a = 67;
int *pa;
pa = &a;
return 666;
}
2.定义的同时初始化
#include"stdio.h"
int main(){
int a = 67 , *pa = &a; # 定义一个指针变量pa,该指针变量pa指向变量a
return 666;
}指向数组指针的初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
181.定义后初始化
#include"stdio.h"
int main(){
int a[5]={12,23,34,45,56};
int *p;
p = a;
return 666;
}
2.定义的用时初始化
#include"stdio.h"
int main(){
int a[5]={12,23,34,45,56}, *p = a;
return 666;
}指向同类型的指针变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
201.定义后初始化
#include"stdio.h"
int main(){
int a=300;
int *pa,*pb;
pa = &a;
pb = pa;
return 666;
}
2.定义时初始化
#include"stdio.h"
int main(){
int a=300, *pa = &a;
int *pb=pa;
return 666;
}
指针和一维数组
1 | int a[5]={1,2,3,4,5}; |
1 | #include"stdio.h" |
进一步验证了,数组名,是数组首元素的地址。既然a的值和p的值一样,那用a和p都行。所以,指针 就是变量。
指针变量就是地址。
所以,访问一维数组元素有下面几种方式
- 利用数组下标访问数组元素(例如 a[3])
- 数组名作为指针访问数组元素(例如 *(a+3))
- 指针变量下标法访问数组元素(例如 p[3])
- 利用指针变量访问数组元素(例如 *(p+3))
结构体
结构体是一种自定义的数据类型,可以将不同类型的数据组合成一个整体。结构体可以包含多个成员变量,每个成员变量可以是不同的数据类型。
结构体的定义方式
方式一
1 | struct books{ |
方式二
1 | struct books{ |
方式三
1 | struct{ |
结构体的初始化
大多数情况下,都是选择,,定义的同时初始化
1 | struct books book1={1001,"computer","张三",34.5} |
demo
1 | #include "stdio.h" |
结构体作为函数参数
常规方法
- 方法1:用结构体变量名作参数。
- 方法2:用指向结构体变量的指针作实参,将结构体变量的地址传给形参。(结构指针作为函数参 数)
结构体和指针
指向结构体变量的指针,叫做结构指针。
1 | 格式 |
1 | struct books{ |
通过结构指针访问取值
1 | #include"stdio.h" |
1 | (*p).n // 得到p指向的结构变量str的成员n的值 |
结构指针作为函数参数
1 | #include"stdio.h" |
共用体(联合体)
在计算机的内存中分配一个特殊的存储空间,由若干个连续的内存单元构成,不同类型的数据都可以放 在该空间。
demo
1 | union chengji{ |
1 | union chengji{ |
- 定义了联合体之后,系统为分配内存。
- 联合体变量占用的存储空间是占有存储空间最大的联合体成员所占的空间。
- 某一个时刻,只有一个成员有效。
链表
- 链表,是动态内存分配的一种结构。按需分配内存。使用动态分配的内存单元存放数据。
- 链表是由若干相同结构类型的元素依次串联而成,使用指针表示两个元素之间的前后关系。
- 链表中的每一个元素都是结点,节点是结构类型。数据域,指针域。
文件操作
C语言中,按照数据存储的编码方式,数据文件分为:
- ASCII文件:以ASCII码进行存储和编码的文件。文件的内容就是字符。可以用记事本打开。(也叫 文本文件)
- 二进制文件:存储二进制数据的文件,是计算机可以识别的机器代码。用记事本打开是乱码。
相关函数
fopen
1 | fopen("文件名","文件打开方式") |
1 | #include <stdio.h> |
- 如果打开成功,则返回指向新打开的文件的指针
- 如果打开失败,返回空指针,NULL,在stdio.h中被定义为 0 。
fclose
demo
1 | #include <stdio.h> |
fputc
使用该函数向文件中写入一个字符。
1 | #include <stdio.h> |
fgetc
1 | ch=fgetc(fp) |
1 | #include <stdio.h> |
fputs
使用该函数向文件中写入一个字符串。
1 | fputs(str,fp) |
1 | #include <stdio.h> |
fgets
读取字符串
1 | fgets(str,n,fp) |
1 | #include <stdio.h> |