C 语言复习总结记录
好用的工具 Xmind 绘制思维导图,方便复习记录
初识 C 语言
1、什么是 C 语言
C 语言是一门通用计算机编程语言,广泛应用于底层开发。设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。是一门面向过程的计算机编程语言,与 C++,Java 等面向对象的编程语言有所不同。
二十世纪八十年代,为避免各开发厂商用的 C 语言语法产生差异,由美国国家标准局为 C 语言制定了一套完整的美国国家标准语法,称为 ANSI C,作为 C 语言最初的标准。
2、第一个 C 程序
#include<stdio.h>// main 函数是程序的入口, 有且只能有一个
int main()
{printf("Hello World!\n");printf("世界欢迎你!\n");return 0;
}
3、数据类型
C 语言没有字符串类型, 字符串类型是靠字符数组实现的
关键字 | 类型 | 长度 |
---|---|---|
char | 字符型 | 1 bytes |
short | 短整型 | 2 bytes |
int | 整型 | 4 bytes |
long | 长整型 | 4 bytes |
long long | 长长整型 | 8 bytes |
float | 单精度浮点型 | 4 bytes |
double | 双精度浮点型 | 8 bytes |
printf("%d\n", sizeof(char));
sizeof 运算符返回的结果是一个 64 位(即 8 bytes)的无符号整型,所以占位符应该是 %llu(注意这里不是 %lld)
printf("%llu\n", sizeof(char));
printf("%llu\n", sizeof(short));
printf("%llu\n", sizeof(int));
printf("%llu\n", sizeof(long));
printf("%llu\n", sizeof(long long));
printf("%llu\n", sizeof(float));
printf("%llu\n", sizeof(double));
4、常量、变量
常量 : 字面常量, const 修饰的常变量, #define 定义的标识符常量, 枚举常量
pai 被称为 const 修饰的常变量 : const 修饰的常变量在语法层面限制了变量不能被直接改变,但是 pai 本质上还是一个变量的,所以叫常变量
#include<stdio.h>#define MAX 100 // #define 定义的标识符常量enum Sex
{Male,FEMale,SECRET
}; //枚举常量int main()
{3.14; //字面值常量const float pai = 3.14;//pai = 5.14; //pai 无法直接修改printf("%d\n", MAX);printf("%d\n", Male); //枚举类型从 0 开始,依次递增 1printf("%d\n", FEMale);printf("%d\n", SECRET);return 0;
}
5、字符串和转义字符
字符串 : 由双引号(Double Quote)引起来的一串字符称为字符串字面值(String Literal),简称字符串
"Hello World!\n";
注 : 字符串的结束标志是一个 \0 的转义字符。在计算字符串长度的时候 \0 是结束标志,不算作字符串内容
char arr1[] = "Hi";
char arr2[] = { 'H','i' };
char arr3[] = { 'H','i','\0' };
printf("%s\n", arr1);
printf("%s\n", arr2); //arr2 无法正常显示, 因为 \0 字符串结束标志
printf("%s\n", arr3);
转义字符 释义
\? 在书写连续多个问号时使用,防止他们被解析成三字母词
\' 用于表示字符常量'
\“ 用于表示一个字符串内部的双引号
\\ 用于表示一个反斜杠,防止它被解释为一个转义序列符。
\a 警告字符,蜂鸣
\b 退格符
\f 进纸符
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\ddd d d d表示1~3个八进制的数字。 如: \130 表示字符X
\xdd d d表示2个十六进制数字。 如: \x30 表示字符0
//打印一个单引号 '
printf("%c\n", '\'');//打印一个双引号 "
printf("%s\n", "\"");
xxx 函数在作用域内没有原型,一般是因为没有引入头文件
例如 : 函数 strlen 在作用域内没有原型, 项目内没有相应的头文件 <string.h>
printf("%llu\n", strlen("abcdef")); //6
// \62 被解析成一个转义字符, \t 水平制表符
printf("%llu\n", strlen("c:\test\628\test.c")); //14
注释分成两种,C语言风格的注释 /* xxx */ ,缺点不能嵌套注释
C++ 风格注释 //xxx ,可注释一行也可多行注释
6、选择和循环语句
选择语句
int score = 0;
scanf("%d", &score);
if (score >= 90)
{printf("恭喜你,及格了");
}
else
{printf("继续加油吧");
}
'scanf': This function or variable may be unsafe. Consider using scanf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
右键项目-> 属性 -> 配置属性 -> C/C++ -> 预处理器定义 -> 输入 _CRT_SECURE_NO_WARNINGS
循环语句
int hour = 0;
scanf("%d", &hour);
while (hour < 10000)++hour;if (hour >= 10000)printf("你实现了目标");
7、函数
作用 : 代码复用,简化逻辑
int add(int x, int y)
{return x + y;
}//避免溢出
long long add(int x, int y)
{//在 + 操作符前强制类型转换, 避免溢出return (long long)x + y;
}int main()
{int num1 = 0, num2 = 0;printf("input num1 = \n");scanf("%d", &num1);printf("input num2 = \n");scanf("%d", &num2);//printf("sum = %d\n", add(num1, num2));printf("sum = %lld\n", add(num1, num2));return 0;
}
8、数组
定义 : 存储一组相同类型元素的集合
int arr[4] = {1 ,2 ,3 ,4}; //定义一个整型数组, 最多存放四个元素
下标从 0 开始,数组可以通过下标进行访问
int arr[4] = {0}; //数组有 4 个元素, 每个都是 0
arr[3] = 1; //把第四个元素置为 1
打印数组
int arr[4] = {1 ,2 ,3 ,4};
for(int i = 0; i < 4; ++i)
{printf("%d\n", arr[i]);
}
9、操作符
算术操作符
+ - * / % (取模操作, 也叫做取余数)
移位操作符
>> <<
移位操作的变量自身不改变
对 char 类型数据进行移位运算时会对它的 ASCII 码值进行操作。
对 byte、short 或者 char 类型数据进行移位操作时,会先把它们整型提升为 int 后再进行运算。
左移运算符,每移动一位,相当于扩大 2 倍。移动几位相当于乘以 2 的几次方
右移运算分两种:
逻辑移位,左边用0填充,右边丢弃;算术移位,左边用原该值的符号位填充,右边丢弃(大多数编译器都是算术右移)
右移运算符,每移动一位,相当于缩小 2 倍。移动几位相当于除以 2 的几次方
位操作符
& ^ |
& 按位与,计算法则:对应的二进制位上,有 0 则为 0,全 1 则为 1
int a = 3;
int b = 5;
int c = a & b;
//00000000000000000000000000000011 ———— a
//00000000000000000000000000000101 ———— b
//00000000000000000000000000000001 ———— c
//所以 a & b = 1
在计算机的计算都是按照补码进行计算的,正数的原码、反码、补码都是一样的,而负数则要将原码准换为反码,再将反码准换为补码进行计算
有一点要注意的是,打印变量时,是用其原码打印的。所以如果结果是负数的补码,我们要算出负数的原码进行打印
int a = 3;
int b = -5;
int c = a & b;
//00000000000000000000000000000011 ———— a
//10000000000000000000000000000101 ———— b
//11111111111111111111111111111010 ———— b的反码
//11111111111111111111111111111011 ———— b的补码//00000000000000000000000000000011 ———— a
//11111111111111111111111111111011 ———— b的补码
//00000000000000000000000000000011 ———— c的补码(因为c的补码为正,正数的原码反码补码相同)
//所以 a & b = 3
|(按位或),计算法则:对应的二进制位上,有 1 则为 1 ,全 0 则为 0
int a = 3;
int b = -5;
int c = a | b;
//00000000000000000000000000000011 ———— a
//10000000000000000000000000000101 ———— b
//11111111111111111111111111111010 ———— b的反码
//11111111111111111111111111111011 ———— b的补码//00000000000000000000000000000011 ———— a
//11111111111111111111111111111011 ———— b的补码
//11111111111111111111111111111011 ———— c的补码
//10000000000000000000000000000100 ———— c的反码
//10000000000000000000000000000101 ———— c的原码
//所以 a | b = -5
^(按位异或),计算原则:对应的二进制位上,相同为0,相异为1
int a = 3;
int b = -5;
int c = a ^ b;
//00000000000000000000000000000011 ———— a
//10000000000000000000000000000101 ———— b
//11111111111111111111111111111010 ———— b的反码
//11111111111111111111111111111011 ———— b的补码//00000000000000000000000000000011 ———— a
//11111111111111111111111111111011 ———— b的补码
//11111111111111111111111111111000 ———— c的补码
//10000000000000000000000000000111 ———— c的反码
//10000000000000000000000000001000 ———— c的原码//所以a ^ b = -8
赋值操作符
A 进行某种操作后再赋值给 A, 例 A += B, 即 A = A + B
= += -= *= /= &= ^= |= >>= <<=
单目操作符
~ 对一个数的二进制按位取反
- 负号
+ 正号
! 逻辑反操作
& 取地址
sizeof 操作数的类型长度(以字节为单位)
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
关系操作符
>
>=
<
<=
!= 用于测试 不相等
== 用于测试 相等
逻辑操作符
&& 逻辑与
|| 逻辑或
条件操作符
exp1 ? exp2 : exp3 //也叫三目表达式
逗号表达式
即 C 语言中的逗号运算符,优先级别最低,它将两个及其以上的式子联接起来,1、从左往右逐个计算表达式,2、整个表达式的值为最后一个表达式的值
exp1, exp2, exp3, … expN
int x = 1, y = 2, z = 3;
下标引用、函数调用和结构成员
[] () . ->
Typora修改代码块默认语言
资源链接 https://blog.csdn.net/weixin_44319595/article/details/133128118
10、关键字
常见的一些关键字
auto break case char const continue default do
double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while
static 关键字
static 用来修饰变量和函数,修饰局部变量-称为静态局部变量,修饰全局变量-称为静态全局变量,修饰函数-称为静态函数。
修饰局部变量
void test() {int i = 0;++i;printf("%d\n", i);
}void test1() {static int i = 0;++i;printf("%d\n", i);
}int main()
{for (int i = 0; i < 10; ++i)test();for (int i = 0; i < 10; ++i)test1();return 0;
}
当 static 修改局部变量时,会改变局部变量的生命周期,当该变量被初始化后,生命周期持续到程序结束
全局变量可跨文件使用
//add.c
int g_val = 1000;//test.c
#include<stdio.h>
extern int g_val;
int main()
{printf("%d\n", g_val);return 0;
}
static 修饰全局变量
//add.c
static int g_val = 1000;//test.c
#include<stdio.h>
extern int g_val;
int main()
{printf("%d\n", g_val);return 0;
}
运行期在 test.c 中的 g_val 会无法链接到源全局变量
static 修饰的全局变量其作用域为该文件
函数跨文件调用方式
1、通过 .h 文件定义,.c 文件实现(推荐)
//A.h
#ifndef __A_H__
#define __A_H__extern int var_A;int func_A();#endif//A.C
#include "A.h"int var_A = 0;
int func_A()
{var_A += 1;return var_A;
}
2、两个 .c 文件间的调用
//add.c
int Add(int x, int y)
{return x+y;
}
//test.c
#include<stdio.h>
extern int Add(int x, int y);
int main()
{printf("%d\n", Add(2, 3));return 0;
}
3、.h 定义函数 .c 调用 .h (这样用可能会导致链接重复定义)
重复定义例子 : A.h 定义了 fa() ,B.h 调用 A.h, B.c 调用 B.h 使用 fa() 定义了一个 fb()。C.h 也调用了 A.h, C.c 调用 C.h 使用 fa() 定义了一个fc()。
main.c 调用 B.h 和 C.h 然后使用 fb() 和 fc() 这时编译报错,重复定义了 fa()
原因 : 预处理器在编译时会把整个头文件直接 copy 过来
解决这个问题,可以使用方式 1
或 static 修饰 (链接时相当于,每个 cpp 文件都有一个单独的 fa 函数,而且本 fa 只会在本 c 文件中产生作用,不会链接到其它 c 文件中)
或 inline (直接告诉编译器复制该方法体到需要的地方,不需要再使用该函数了)
static 修饰函数
//add.c
static int Add(int x, int y)
{return x+y;
}
//test.c
#include<stdio.h>
extern int Add(int x, int y);
int main()
{printf("%d\n", Add(2, 3));return 0;
}
运行期在 test.c 中的 g_val 会无法链接到源 Add 函数实现,static 修饰的函数其作用域为该文件
#define 关键字
定义常量和宏
//define定义标识符常量
#define MAX 1000//define定义宏
#define ADD(x, y) ((x)+(y))
#include <stdio.h>
int main()
{int sum = ADD(2, 3);printf("sum = %d\n", sum);sum = 10*ADD(2, 3);printf("sum = %d\n", sum);return 0;
}
#define 宏定义会在程序编译的 预处理阶段 在调用宏的位置进行文本内容的直接替换
因此对于宏函数 Add 加法函数要注意一些可能会报错的问题,
如果 #define ADD(x,y) x+y
则 ADD(x,y)*3 会被替换为 2 + 3 * 3 = 11如果 #define ADD(x,y) (x+y)
则 ADD(a|b, a&b) 会被替换为 2 | 3 + 2 & 3 //算术运算符比逻辑运算符优先级高
11、指针
内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。
为了有效使用内存,把内存划分成一个个小的内存单元,每个内存单元的大小是 1 个字节。为了能访问到内存的每个单元,给内存单元进行了编号,这些编号被称为该内存单元的地址。
变量是创建内存中的(在内存中分配空间),每个内存单元都有地址,所以变量也是有地址的
#include <stdio.h>
int main()
{int num = 10;# //取出 num 的地址//这里 num 占用 4 个字节,每个字节都有地址,取出的是第一个字节的地址(即 num 变量的首地址)printf("%p\n", &num); //打印地址,%p 是以地址形式打印return 0;
}
那地址如何存储,需要定义指针变量
int num = 10;
int *p; //p为一个整形指针变量
p = #
*p = 20; //修改 p 指向的内存空间的值//同理其它类型的指针
char ch = 'a';
char *p; //p为一个整形指针变量
p = &ch;
*p = 'b'; //修改 p 指向的内存空间的值
指针的大小
在 32 位平台是 4 个字节,64 位平台是 8 个字节
#include <stdio.h>
//指针变量的大小取决于地址的大小
// 32 位平台下地址是 32 个 bit 位(即 4 个字节)
// 64 位平台下地址是 64 个 bit 位(即 8 个字节)
int main()
{printf("%llu\n", sizeof(char *)); //8printf("%llu\n", sizeof(short *)); //8printf("%llu\n", sizeof(int *)); //8printf("%llu\n", sizeof(double *)); //8return 0;
}
12、结构体
结构体是 C 语言中描述复杂类型的关键字。比如描述学生,学生包含:名字 + 年龄 + 性别 + 学号 这几项信息。
struct Stu
{char name[20]; //名字int age; //年龄char sex[5]; //性别char id[15]; //学号
};//常见用法, 这样可以避免初始化的时候多写 struct 关键字
typedef struct Stu
{char name[20]; //名字int age; //年龄char sex[5]; //性别char id[15]; //学号
} Stu;
结构体初始化
//打印结构体信息
Stu stu = { "张三", "10", "男", "2024101" };//. 为结构成员访问操作符
printf("name = %s, age = %d, sex = %s, id = %s \n", stu.name, stu.age, stu.sex, stu.id);//-> 操作符
Stu* ptr = &stu;
printf("name = %s, age = %d, sex = %s, id = %s \n", ptr->name, ptr->age, ptr->sex, ptr->id);
turn 0;
}
12、结构体
结构体是 C 语言中描述复杂类型的关键字。比如描述学生,学生包含:名字 + 年龄 + 性别 + 学号 这几项信息。
struct Stu
{char name[20]; //名字int age; //年龄char sex[5]; //性别char id[15]; //学号
};//常见用法, 这样可以避免初始化的时候多写 struct 关键字
typedef struct Stu
{char name[20]; //名字int age; //年龄char sex[5]; //性别char id[15]; //学号
} Stu;
结构体初始化
//打印结构体信息
Stu stu = { "张三", "10", "男", "2024101" };//. 为结构成员访问操作符
printf("name = %s, age = %d, sex = %s, id = %s \n", stu.name, stu.age, stu.sex, stu.id);//-> 操作符
Stu* ptr = &stu;
printf("name = %s, age = %d, sex = %s, id = %s \n", ptr->name, ptr->age, ptr->sex, ptr->id);