Basic_Syntax
数据类型
基本类型
整型int(4B=32b,可以表示2^32)
long long(8B=64b,可以表示2^64个数)
1
2
3
4
5
6
7
int main() {
int a, b;
scanf("%d %d", &a,&b);
printf("%d", a+b);
}字符型char(1B=8b)
用单引号
''括起来的,只能包含一个字符1
2
3
4
5
6
7
int main() {
char c = 'a';
printf("%c", c);
return 0;
}浮点型
- 单精度float(4B=32b)
- 双精度double(8B=32b)
字符串常量
使用双引号
""将字符串括起来。控制字符
字符串里可以有3种控制字符,
%d十进制整数%f浮点数%c字符当然字符串的后面需要
,来隔开参数
构造类型
- 数组[]
- 结构struct
- 联合union
- 枚举enum
指针类型
- 指针*
空类型
- void
混合运算
C是根据表达式中的数据类型来得到结果的类型
e.g.
因为5和2是整型,得到整形的2,再通过类型转换等到2.000000
1 | float f = 5 / 2; |
那么,我们怎么输出2.5呢?
直接让表达式中的一个数的数据类型变成浮点类型(强制转换)
1 | float f = (float)5 / 2; |
scanf的原理
- 我们在终端输入时,当终端遇到
\n时,才进行I/O操作,即将内容复制到内存中的缓存区中(包括\n)。 - scanf()会不停对缓冲区检测。当内存中的缓冲区为空时,scanf()会对进程进行阻塞。
缓冲区不为空时,scanf()会通过给定的字符与缓冲区匹配,控制字符(%d,%f,%c…)与缓冲区转化,并通过&取走。
printf()正好与scanf()相反,通过控制字符(%d,%f,%c…),转换成字符串,并输出到屏幕
1 |
|
%d %f %lf
清除缓冲区的非空字符(清除一开始的空白和\n)开始读,直到遇到空白和\n结束。所以\n会留在缓冲区。
这里%d与%d中间加不加空格都可以。如果没有空格,scanf中的%d跳过空格。如果有,匹配空格 。
1 |
|
在输入中两个数要加空格
1 | 1 2//输入 |
如果我这样,肯定是不行的。因为第2个%d读的是逗号,scanf并不会赋值给b,而是直接结束返回。b还是原来的未定义值。
1 | 1,2//输入 |
%c
scanf("%c", ch)它读取一个字符。关键是它不会清除空白,也不会清除\n
整形数在0-128直接,可以用%c输出
%s
清除缓冲区的非空字符(清除一开始的空白和\n),到空白和\n结束
1 | int main() { |
返回值
出错时,返回
EOF=1多次Ctrl+z结束while循环,使scanf出错
匹配缓冲区失败,返回0
E.g.当用%d遇到字符a时。
匹配缓冲区成功变量的个数
scanf循环输入
循环输入整数
1 |
|
循环输入字符
1 |
|
循环输入混合类型数据
- 要点:在
%c前加入一个空格
1 |
|
运算符与表达式
算术运算符(+, -, *, /, %)
由算术运算符所组成的表达式,称算术表达式
逆序输出 1
2
3
4
5
6
7
8
9
10
11
12
int main() {
int n;
scanf("%d", &n);
while (n!=0)
{
printf("%d", n % 10);
n = n / 10;
}
return 0;
}关系运算符(>, <, ==, >=, <=, !=)
C语言认为一切非0值都是真,C语言没有布尔类型
不要用关系运算符连写(1<a<8),是错误的
逻辑运算符(!, &&, ||)
1
2
3
4
5
6
7
8
9
int main() {
int a = 18;
if (a > 2 && a < 10)
printf("a is right");
else
printf("a is wrong");
return 0;
}赋值运算符(=)
赋值运算符的左边只能放变量
逗号运算符(,)
逗号表达式的整体值是最后一个表达式的值
自增自减运算符(++,–)
注意:
i++和++i如果是
i++那么,就让等到本语句运行完后,最后才将i=i+1,记住它是直接改变i的值如果是
++i,直接按优先级运算即可。1
2
3
4
5
6
7
8
int main() {
int i = -1;
int j;
j = i++ > -1;//任何时候,先去掉 后++,等到本语句运行完后,再执行++
printf("i=%d,j=%d", i, j);
return 0;
}sizeof运算符
打印变量的占据的字节数
循环
- while
- for
当需要运行指定数量的循环时,就用for。当需要某个条件使循环结束时就用while
for的执行循序
for(表达式1;表达式2;表达式3)语句;
for循环语句的执行过程如下
(1)先求解表达式1。
(2)求解表达式2,
为真(值为非0),则先执行for语句中指定的内嵌语句,后执行 第(3)步。
为假(值为0),则结束循环,转到第(5)步。
(3) 求解表达式3
(4) 转回第(2)步继续执行。
(5) 循环结束
数组
数组名[常量表达式],数组名是一个地址常量。
1 | int main() { |
数组具有以下特点:
- 相同数据类型
- 过程中需要保存数据
- 空间大小指定
访问越界:
访问越界会造成:Stack around the variable ‘a’ was corrupted.
1 |
|
传递整型数组
当我们传递数组时,不会把整个数组传递过去。只会传递数组的起始地址。
所以,我们传递数组时,还要传递数组长度
1 |
|
字符数组
我们用字符数组来存储字符串,字符串的结束标志为\0,所以,数组的长度要比字符串长度至少多1个字节。
如果字符数组长度大于字符串长度,会自动添加\0
1 | char c[6] = "hello"; |
还可以这样
1 | char c[] = "hello"; |
传递字符数组
由于字符串的最后一个字节有\0,所以我们可以利用最后一个字节为假,作为结束符号。这样,就不用像整型数组一样,传递长度了。
1 |
|
输入一行字符
我们可以用gets()来读取一行,它读到\n才开始赋值(并把\n换成\0)。他不像scanf那样,读到空格或\n就赋值
1 |
|
字符串操作(str系列)
有const的话,即可以放字符串常量,也可放变量
strlen()
unsigned int strlen(const char *str)strcpy()
char *strcpy(char *dest,const char *src)strcmp()
int strcmp(const char *str1, const char *str2)当str1指向的字符串大于str2指向的字符串时,返回正数。
当str1指向的字符串等于str2指向的字符串时,返回0。
当str1指向的字符串小于str2指向的字符串时,返回负数。
strcat()
char * strcat(char *dest,const char *src)此函数原型为 char *strcat(char *dest, const char *src).
功能为连接两个字符串,把src连接到dest后面;返回dest地址
指针
指针就是地址变量,用于存放地址。
使用场景:传递和偏移
指针变量也是有类型的。(e.g.
int*,float *…)只能把同类型的地址存放在同类型的指针变量中。
指针的声明
int * p表明
p是指针*p的类型是int*是告诉编译器p是一个指针
声明多个指针
int *p1, *p2, *p3
可以看到指针是用于存放地址的,其空间大小,就表示地址总线的条数(1条=1b),32位就是4字节。
1 | int* p; |
注意:指针的声明和使用是两个东西
指针的偏移
对指针变量进行加减运算。如果+1,并不是+1B(按字节编号),而是加了指针类型所占据的字节。
e.g.
1 | int a[5]; |
指针的++和–
任何时候都先把 后++ 去掉,等整个语句运行完后。再按照优先级对相应的变量执行 后++
1 |
|
指针的注意事项
*p:这个东西好像是一个指针变量的值,但这样理解并不深入。
下面的例子,我们应该把*p==a[0],改变*p就是改变a[0]
1 | int a[3] = { 2,7,8 }; |
1 |
|
内存
代码区:存放代码
数据区:全局变量,字符串常量(程序中所有的相同的字符串常量,只存放一个)。这个区只能读,不能写。
这里”hello”是字符串常量,p指向数据区,所以不能更改。而
char c[20] = "hello";是让字符串常量的每一个字符复制到栈区。所以可以,更改1
2
3
4
5
6
7
8
9
10
11
int main() {
char* p = "hello";
char c[20] = "hello";
//p[0] = 'a'; //不能对常量更改
c[0] = 'a'; //可以对栈区更改
printf(c);
}栈空间:整型,浮点型,字符型变量,数组变量,函数,都在栈空间中。
栈空间在编译的时候就确定好。
堆空间:需要申请,也需要释放
动态申请堆
malloc():
单位:字节B
返回值:无类型指针。所以,要用强制类型转换,转换成指定类型的指针。
calloc()
申请空间,并对空间赋值为0
free()
释放空间必须与原来申请的指针一致,不能偏移。
1 | free(p); |
e.g.
1 |
|
函数
函数调用的本质
CPU通过PC得到main函数(代码区)的地址,main函数创建栈帧用于存放局部变量。main可能调用其他函数fun,PC就会指向fun的地址,fun函数也会申请栈帧。当fun函数结束,这个栈帧就会释放。(所以,栈区在逻辑上就数据结构上的栈,后进先出),PC就会返回主调函数。
函数传递的参数是值传递,就是复制一个值给函数。
函数在栈中,其栈空间会随函数的调用而开辟,到结束而释放。
注意:自己申请的堆空间不会随函数的结束而释放。
1 |
|
1 |
|
递归的本质
函数调用自己,pc不断重回函数起始位置,但代价是每调用一次,在栈区就会多申请一个栈帧。如果,这样一直调用就会导致栈溢出,所以我们必须设置递归出口。
the process of repeating a function, each time applying it to the result of the previous stage.

要记住的二要素(分治合)
- 大问题——>小问题的递推关系(假设小问题已解决)
- 找到最小问题
递归就是不断压栈,得到最小问题。然后不断弹栈,计算出更大的问题。
1 | void func(int n){//大问题 |
n的阶乘
求n!(大问题),小问题是(n-1)!,递推关系:n!=n*(n-1)!
最小问题n=1
1 |
|
结构体
声明结构体类型
1
2
3
4
5
6
7
8struct student //struct是关键字,student是数据类型
{
int num;
char name[20];
char sex;
int age;
float score;
}; //最后一定加分号定义变量
1
2struct student s = { 1,"ferry",'m',20,100.0 };//定义,初始化。struct关键字还要写
struct student sarr[3];
typedef
由于定义一个结构体变量要写struct关键字,和类型,所以很麻烦,所以我们使用typedef来给结构体一个别名。
这样就可以在定义的时候,不用写关键字struct
1 | typedef struct student { |
结构体的大小
这里涉及到内存对齐的知识。
对齐的目的是为了访问提高cpu访问内存的效率
结构体指针
1 | struct student s = { 1,"ferry",'m',20,100.0 };//定义,初始化。struct关键字还要写 |
->与后++优先级
1 |
|
C++的引用
首先要把文件类型改成.cpp
更改值
引用是为了在我们要在子函数中更改变量的时候,不用写指针的*,传递变量名就可以直接更改主函数中变量的值。
1 |
|
更改指针
引用是为了在我们要子函数中更改指针的时候,不用写指针的**,传递指针就可以直接更改主函数中的指针。
1 | void modify_point(int*& p) { |
小常识
程序阻塞的情况:
- I/O操作。(scanf, gets 检测到缓冲区为空)
- 无限循环
