C1.1 c语言基础回顾
C基础
1 如何使用C语言
2.1 . 关键字
- 32个关键字:
变量类型关键字:int char float double void long short
存储类型关键字:auto register static extern const
符号类型关键字:sigend unsigend volatile(仅可特定指针访问)
条件语句关键字: if else switch case default
循环关键字:for while do goto continue break return
自定义数据结构关键字:struct union enum
其他关键字:sizeof typeof
2.2 . 标识符
- 自定义的 变量名、函数名、结构体名、枚举名、宏名等。
-遵循以下规则: - 算术运算符:+ - * / % ++ —
- 关系运算符:==!= < > <= >=
- 逻辑运算符:&& ||!
- 赋值运算符:= += -= *= /= %= &= |= ^= <<= >>=
- 位运算符:& | ^ ~ << >>
- 其他运算符:sizeof & & &&, () [] ->. ? : sizeof
- 运算符一般优先级:从高到低,位运算符>算数运算符>关系运算符>逻辑运算符>赋值运算符。
- 运算符其实是一种特殊的函数。
2.4 . 变量
变量(函数等同理)的实现过程: 申明变量 -> 定义变量 -> 赋值 -> 使用变量。其中,申明变量是指声明变量的类型、变量名和变量的存储方式,定义变量是指为变量分配内存空间
。变量的申明和定义一般通过编译器缺省的机制完成,也就是只需定义变量即可。
注意:const修饰的变量不能被修改。但是仅仅是不可通过变量名修改,而是需要通过指针修改。
1 | const int a = 10; // 申明常量变量 |
2.5 . 输入输出1(字符)
字符的输入输出可以用printf()和scanf()函数实现。
这两个函数分别要对应输入缓冲区和输出缓冲区。
- 输入缓冲区:scanf 不会立即从输入源读取数据。相反,它依赖于一个输入缓冲区,该缓冲区存储了之前由用户或其他来源提供的输入数据。当 scanf 被调用时,它首先尝试从这个缓冲区中读取需要的数据。如果输入不符合预期的格式,scanf 可能会留下一些字符在输入缓冲区中,这可能会导致后续的 scanf 调用出现问题。
1 |
|
原因是读缓冲未结束,导致将\n也读进来b。
解决方法:1
2
3
4
5
6
7
8
9
10
11
12
int main() {
int a,c;
char b;
printf("Enter a character: \n");
scanf("%d", &a);
getchar(); //清空输入缓冲区
scanf("%c%d", &b,&c);
printf("You entered: %c\n", c);
return 0;
}
刷新输入缓冲取的函数为fflush(stdin),但是函数不推荐使用。
- 输出缓冲区较输入缓冲区相似。
在linux中使用unistd.h中的sleep()函数时若不刷新输出缓冲区,则会导致无法输出。1
2
3
4
5
6
7
8
9
10
11
12
13
int main ()
{
while(1)
{
printf("1");
sleep(1);
}
}
解决办法:1
2
3
4
5
6
7
8
9
10
11
12
int main ()
{
while(1)
{
printf("1");
sleep(1);
fflush(stdout);
}
}
或者在printf()函数中加入\n
或endl
来刷新输出缓冲区。
2.6 . 数据结构1(数组)
- array[]:数组名,数组元素的个数。
- array:为指向数组的指针。
- &array:数组第一个元素的地址。
1
int array[10]; //申明一个10个整数的数组
2.6.1 数组越界访问
数组越界访问会导致隐性错误。1
2
3
4
5
6
7
8
9
10
11
12
void main(){
char str1[] = "hello1";
char str2[] = {'h', 'e', 'l', 'l', 'o','2'};
int len1 = strlen(str1);
int len2 = strlen(str2);
printf("The length of str1 is %d\n", len1);
printf("The length of str2 is %d\n", len2);
printf("str2:%s",str2);
}
上述代码中,str2没有以空字符结尾,导致长度计算错误。
但由于编译器为str1和str2分配了连续的内存空间,因此可以通过数组下标访问到其中的元素。
即输出1
2
3
4
5
The length of str1 is 6
The length of str2 is 12
str2:hello2hello1
2.7 . 数据结构2 (结构体/联合体/枚举)
- 结构体
- 注意:结构体名,不是指针而是变量名。
- 注意:结构体结合typedef时,重名还为执行。不可立即使用。
- 联合体
- for 循环:for (初始化; 条件表达式; 迭代表达式) {语句}
- while 循环:while (条件表达式) {语句}
- do-while 循环:do {语句} while (条件表达式);
- goto 语句:goto label;
- if-else 语句:if (条件表达式) {语句} else {语句}
- switch-case 语句:switch (表达式) {case 常量表达式1:语句; case 常量表达式2:语句; default:语句;}
- 函数的声明:返回类型 函数名(参数类型 参数名, …);
- 函数的定义:返回类型 函数名(参数类型 参数名, …) {函数体}
- 函数的调用:函数名(实参, …);
- 递归函数:函数调用自身。
- 指针变量:指针变量存储的是内存地址。
- 数组指针:指向数组的指针。但是数组指针无法将数组的信息
- 指针数组:数组中每个元素都是一个指针。
- 常量指针:指向常量的指针。
- 指针常量:指针指向常量。
- strlen():计算字符串的长度。
- strcmp():比较两个字符串是否相等。
- strcpy():复制一个字符串到另一个字符串。
- srecat():连接两个字符串。
- strstr():查找子串在字符串中的位置(地址)。
scnaf(“%s”,str)
1
2
3
4
5
6
7
8
9
10
int main()
{
char str[100];
scanf("%s",str);
printf("%s",str);
return 0;
}scanf()遇到空格或回车时,会自动结束输入。
gets()
- fgets()
- 如果 str1 小于 str2(即 str1 在字典顺序上排在 str2 之前),则返回一个小于 0 的负数。
- 如果 str1 等于 str2,则返回 0。
- 如果 str1 大于 str2(即 str1 在字典顺序上排在 str2 之后),则返回一个大于 0 的正数。
- 其返回值是第一个不同的字符的差值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main()
{
char str1[] = "hello1";
char str2[] = {'h', 'e', 'l', 'l', 'o','2'};
int len1 = strlen(str1);
int len2 = strlen(str2);
printf("The length of str1 is %d\n", len1);
printf("The length of str2 is %d\n", len2);
printf("str2:%s",str2);
int cmp = strcmp(str1,str2);
printf("cmp:%d\n",cmp);
return 0;
} - 预编译指令:#ifndef 和 #define。
多文件编译可使用makefile。对于简单的程序也可直接使用
gcc -o main main.c -I. -L. -lmylib
。头文件编写原则:
- 模块间不会互相依赖时,每个模块定义一个头文件即可,主函数文件包含所有模块的头文件即可(主函数一般不用设置头文件)。
- 模块间存在依赖(循环调用)时,不引入复杂的调用规则就只有重新设计了。为了避免这种情况模块间的调用可以利用主函数实现。
- 全局变量和静态变量;可以通过extern关键字声明变量在其他文件中可见。但是极容造成命名冲突。故不推荐使用。
- 可以使用地址传递的方式,访问其它文件的数据。
- realloc在源内存仍然可以存储数据时不会改变内存地址,
- 而仅将多余的内存空间标记为可用(不再占用),越界访问仍可读到数据。故应注意引索的设置。
- 此规则对于free也适用,故在free释放内训后需要将指针指向NULL避免越界访问。
- 代码段:存放代码和常量(变量只读)数据。
- 静态区:存放静态变量。
- 全局变量区:存放全局变量。
- 栈:存放函数调用时的临时变量。
- 堆:存放动态分配的内存。
- 空指针区:存放空指针。
- 数组越界访问:数组的下标越界访问,会导致程序崩溃或其他不可预知的错误。
- 指针越界访问:指针访问的地址越界,会导致程序崩溃或其他不可预知的错误。
- 野指针:野指针,是指指针指向的地址不是有效的内存地址,会导致程序崩溃或其他不可预知的错误。可采用定义指针时初始化为NULL(空指针)的方式避免此类错误。
1
2int *p;//野指针
printf("%d", *p);//此处p指针指向的地址是无效的,会导致程序崩溃空指针会报错。用以提示修改代码。1
2int *p = NULL;//初始化为NULL
printf("%d", *p); - 内存泄漏:程序运行过程中,申请了内存,但没有释放,导致系统内存不足,导致程序崩溃或其他不可预知的错误。
- 死锁:多个进程因互相等待而陷入僵局,导致程序崩溃或其他不可预知的错误。
- 栈溢出:函数调用层次过深,导致栈内存溢出,导致程序崩溃或其他不可预知的错误。
- 堆溢出:申请的堆内存过大,导致系统内存不足,导致程序崩溃或其他不可预知的错误。
- 格式化字符串漏洞:通过格式化字符串漏洞,可以注入恶意代码,导致程序崩溃或其他不可预知的错误。
1 | { |
1 | //错误 |
</ol>
2.8 . 循环
2.9 . 条件语句
2.10 . 函数
2.11 . 指针
2.11.1 数组指针与指针数组
1 | int main() { |
2.11.2 常量指针与指针常量
1 | int main() { |
2.11.3 函数指针
用于函数重载,由于c中本身不支持函数重载,因此需要通过函数指针来实现。不过需要手动指定函数指针。
1 |
|
2.12 . 字符串处理函数
2.12.1 常见字符串处理函数
2.12.2 strlen
1 |
|
结果无法输出,str2没有以空字符结尾,导致长度计算错误。
2.12.3 字符串输入
长度无法限制,会导致缓冲区溢出。
1 |
|
gets()会读入空格,当不会读入’\n’。
1 |
|
fgets()可读入包行空格和最后的’\n’。
2.12.4 strcmp
strcmp 的返回值取决于两个字符串之间的关系:
2.12.5 sprintf
1 | void main() |
注意下为错误的写法:1
2
3
4
5
6void main()
{
char *str;
sprintf(str,"%d%d",1,2);
printf("%s",str);
}对于多字节
:多字节字符和宽字符:对于多字节字符集或宽字符,应该使用相应的函数如 wcscmp 或 mbscmp。
2.13 . 预处理器
2.14 . 多文件操作
2.14.1 头文件
两种防止头文件重复包含的方式:
1 |
|
1 |
|
2.14.2 多文件资源访问
定义文件1 file1.c
file1.h
:
1 |
|
1 | int main() |