【笔记】C语言学习笔记

前言

C语言(英语:C Language)是一种通用的、过程式编程编程语言,支持结构化编程、词法作用域和递归,使用静态类型系统,并且广泛用于系统软件与应用软件的开发。(维基百科

编译器

  1. 预处理:展开头文件、替换宏、删除注释,生成.i文件
  2. 编译:将.i文件转为汇编代码.s文件
  3. 汇编:将.s文件转为机器码目标文件.o(仅包含当前源文件的机器码,无外部依赖解析)
  4. 链接:将当前.o文件、依赖的系统库(如libc)、其他.o文件(若多个`.c文件一起编译)进行链接,解析所有外部符号(如printf、全局变量、跨文件函数),最终生成可执行文件

-o <file>:指定输出的文件存放路径
-std=c89:使用c89规范编译

仅预处理

  • 预编译会把引入的头文件的源码直接引入
  • 预编译会去除注释
1
gcc -E <file>.c

仅编译

  • 预处理=>编译=>汇编
1
gcc -c <file>.c

编译生成可执行文件

  • 预处理=>编译=>汇编=>链接
1
gcc <file>.c
  • 一次性编译多个C语言源码
1
gcc <file_1>.c <file_1>.c

查看库

  • 查看一个编译后的文件使用了什么库
1
ldd <file>

头文件

引入头文件

  • 通过include关键字引入头文件

系统目录下的头文件

1
#include <头文件名.h>

当前目录下的头文件

1
#include "头文件名.h"

查看引入什么头文件

Linux

1
man 3 printf

Windows

1
printf site:learn.microsoft.com

头文件存储位置

Linux

  • /usr/include/stdio.h

Windows

Visual Studio(MSVC)
  • 标准C库:C:\Program Files (x86)\Microsoft Visual Studio\<ver>\<Edition>\VC\Tools\MSVC\<toolset-ver>\include
  • WindowsSDK:C:\Program Files (x86)\Windows Kits\10\Include\<sdk-ver>\ucrt
MinGW(轻量GCC)
  • 64位
    • <root>\mingw64\include
    • <root>\mingw64\x86_64-w64-mingw32\include
    • <root>\mingw64\lib\gcc\x86_64-w64-mingw32\<ver>\include
  • 32位:<root>\mingw32\i686-w64-mingw32\include
Cygwin(完整GCC)
  • Cygwin根:<cygwin-root>\usr\include
  • 32位GCC:<cygwin-root>\usr\i686-pc-cygwin\include
  • 64位GCC:<cygwin-root>\usr\x86_64-pc-cygwin\include

主函数

  • 主函数返回值为0表示正常退出
1
2
3
4
5
6
7
int main()
{

...

return 0;
}

注释

  • 块注释是C语言标准的注释方式
1
/* 块注释 */
  • 行注释是从C++继承过来的,现在也可以在C语言中使用
1
// 行注释

执行操作系统命令

<command>:在当前操作系统下的命令

1
system("<command>");

输出语句

putchar输出字符

1
2
3
char 变量名;

putchar(变量名);

puts输出字符串

  • 末尾添加换行符
1
2
3
char 变量名[数组长度] = {0};

puts(变量名);

fputs输出字符串

  • 末尾不会添加换行符
1
2
3
char 变量名[数组长度] = {0};

fputs(变量名, stdout);

printf格式化输出

  • 末尾不会添加换行符
1
#include <stdio.h>
1
2
3
char 变量名[数组长度] = {'1', 0};

printf("文本内容%s", 变量名); // "文本内容1"
格式说明符 数据类型 描述
%hd short int 短整型数据
%hu unsigned short int 无符号短整型数据
%d%i int 有符号十进制整型数据
%u unsigned int 无符号十进制整型数据
%o unsigned int 无符号八进制整型数据
%x unsigned int 无符号十六进制整型数据,字母小写
%X unsigned int 无符号十六进制整型数据,字母大写
%ld long 有符号十进制长整型数据(32位或64位)
%lu unsigned long 无符号十进制长整型数据(32位或64位)
%lo unsigned long 无符号八进制长整型数据(32位或64位)
%lx unsigned long 无符号十六进制长整型数据,字母小写(32位或64位)
%lX unsigned long 无符号十六进制长整型数据,字母大写(32位或64位)
%lld long long 有符号十进制长整型数据(64位)
%llu unsigned long long 无符号十进制长整型数据(64位)
%llo unsigned long long 无符号八进制长整型数据(64位)
%llx unsigned long long 无符号十六进制长整型数据,字母小写(64位)
%llX unsigned long long 无符号十六进制长整型数据,字母大写(64位)
%f floatdouble 单精度浮点型数据或双精度浮点型数据
%e double 双精度浮点型数据,科学记数法,字母小写
%E double 双精度浮点型数据,科学记数法,字母大写
%c char 字符型数据
%s char* 单字节字符串型数据
%S wchar_t* 宽字节字符串型数据
%p void* 十六进制指针数据
%% - 输出一个百分号

输入语句

输入字符

  • 接收一个字符,获取它的ASCII码值
1
int 变量名 = getchar();

输入字符串

fgets输入字符串

  • 遇到回车符(\n)、文件结束符(EOF),达到数组长度会自动结束
  • 换行符作为输入
  • 如果输入超出字符串长度,不会报错,会立即结束
1
2
3
char 变量名[数组长度] = {0};

fgets(变量名, sizeof(变量名), stdin);

scanf格式化输入

  • 遇到空白字符会自动结束
  • 换行符不会作为输入
  • 如果输入超出字符串长度,报错
1
2
3
char 变量名[数组长度] = {0};

scanf("%s", &变量名);
报错
  • 在VisualStudio中,scanf被认为是不安全的,所以使用scanf会报错
解决问题
方法一
  • 引入不警告宏函数
1
#define _CRT_SECURE_NO_WARNINGS
方法二
  • 通过警告编号屏蔽警告
1
#pragma warning(disable:4996)

变量

声明变量

1
数据类型 变量名;

定义变量

1
数据类型 变量名 = 值;

变量赋值

1
变量名 = 值;

常量

定义宏常量

  • C语言通常使用define定义常量
  • 宏常量必须在文件头部定义
  • 定义宏常量末尾不需要添加;
1
#define 常量名 值

定义const常量

  • C++通常使用const定义常量
  • const常量在函数内定义
1
const 数据类型 常量名 = 值;

基本数据类型

整型

类型 描述 占用空间
shortsigned short 有符号短整型 2字节
unsigned short 无符号短整型 2字节
intsigned int 有符号整型 4字节
unsigned int 无符号整型 4字节
longsigned long 有符号长整型 4字节(Windows系统、32位操作系统)、8字节(64位操作系统)
unsigned long 无符号长整型 4字节(Windows系统、32位操作系统)、8字节(64位操作系统)
long longsigned long long 有符号长长整型 8字节
unsigned long long 无符号长长整型 8字节
1
2
3
4
5
6
signed int 变量名 = 1;
unsigned int 变量名 = 1U;
signed long 变量名 = 1L;
unsigned long 变量名 = 1UL;
signed long long 变量名 = 1LL;
unsigned long long 变量名 = 1ULL;

进制表示

  • 描述二进制的数值,需要以0b开头
1
unsigned short binary = 0b1;
  • 描述八进制的数值,需要以0开头
1
unsigned short octal = 07;
  • 描述十六进制的数值,需要以0x开头
1
unsigned short hexadecimal = 0xF;

浮点类型

类型 描述 占用空间
floatsigned float 有符号单精度浮点型数据 4字节
unsigned float 无符号单精度浮点型数据 4字节
doublesigned double 有符号双精度浮点型数据 8字节
unsigned double 无符号双精度浮点型数据 8字节
long doublesigned long double 有符号长双精度浮点型数据 12字节
unsigned long double 无符号长双精度浮点型数据 16字节

字符型

  • 字符型的本质是1字节整型

在C语言中没有1字节的整型数据类型,所以用字符型代替

1
char 变量名 = '';

获取数据占用内存空间

1
unsigned int 变量名 = sizeof(变量)

字符串型(字符数组)

  • 字符串在内存中是一段连续的char空间,以\0结尾
1
char[] 变量名 = "字符串";

运算符

算数运算符

  • +(正号)、-(负号)
  • +(加号)、-(减号)、*/%
  • ++(自增)、--(自减)

关系运算符

  • 返回1表示真,返回0表示假

  • ><>=<===!=

逻辑运算符

  • 返回1表示真,返回0表示假

  • &&||!

赋值运算符

  • =+=-=*=/=%=
  • &=|=^=<<=>>=

按位运算符

  • &|^~<<>>

内存地址运算符

  • &(取地址运算符)、*(解引用运算符)

逗号运算符

  • ,(先计算,左边的表达式,后计算,右边的表达式,但最终只返回,右边的表达式结果)

分支语句

  • 布尔表达式1表示真,返回0表示假

if语句

1
2
3
4
if (布尔表达式)
{
布尔表达式成立时执行的代码;
}
1
2
3
4
5
6
7
8
if (布尔表达式)
{
布尔表达式成立时执行的代码;
}
else
{
布尔表达式不成立时执行的代码;
}
1
2
3
4
5
6
7
8
9
10
11
12
if (布尔表达式1)
{
布尔表达式1成立时执行的代码;
}
else if (布尔表达式2)
{
布尔表达式2成立时执行的代码;
}
else
{
以上布尔表达式都不成立时执行的代码;
}
1
2
3
4
5
6
7
if (布尔表达式1)
{
if (布尔表达式2)
{
布尔表达式12都成立时执行的代码;
}
}

switch语句

  • C语言的switch语句具有穿透行为,在每个case语句中,必须使用break跳出seitch语句
1
2
3
4
5
6
7
8
9
10
11
12
switch (变量名)
{
case1:
变量值为值1时执行的代码
break;
case2:
变量值为值2时执行的代码
break;
defautl:
变量值不为以上任何值时执行的代码
break
}

循环语句

  • 布尔表达式1表示真,返回0表示假

while 语句

1
2
3
4
while (布尔表达式)
{
布尔表达式成立时反复执行的代码;
}

do…while 语句

1
2
3
4
do
{
布尔表达式成立时反复执行的代码;
} while (布尔表达式);

for 语句

1
2
3
4
for (int i = 起始值; 布尔表达式; i++)
{
布尔表达式成立时反复执行的代码;
}

break关键字

  • 跳出循环
1
2
3
4
while (1)
{
break;
}

continue关键字

  • 结束当前循环,开始下一次循环
1
2
3
4
while (1)
{
continue;
}

三元运算

1
数据类型 变量名 = 布尔表达式 ? 布尔表达式成立时返回的结果 : 布尔表达式不成立时返回的结果;

跳转语句

  • 无条件跳转到指定位置

  • 定义一个跳转点

1
跳转点:
  • 立即跳转到指定跳转点
1
goto 跳转点

终止程序

1
exit(0);

存储类型修饰符

  • 类型限定符和存储类型修饰符可以同时修饰同一变量,且没有顺序要求

auto

  • 通过auto关键字修饰的变量是自动变量,自动变量在函数调用时创建,函数使用完后销毁
  • 代码块内定义的变量一定都是自动变量,auto关键字可以省略
1
2
3
{
auto 数据类型 变量名 = 值;
}

static

  • 通过static关键字修饰的变量和函数是静态的,静态变量和静态函数只初始化一次,在程序加载到内存的时候就创建,直到程序运行结束才销毁
    • 全局静态变量只能在定义它的文件内使用
    • 全局静态函数只能在定义它的文件内使用
1
2
3
4
5
6
7
8
9
10
11
12
static 数据类型 全局静态变量名 = 值;

static void 全局静态函数
{
...
}

int main()
{
static 数据类型 局部静态变量名 = 值;
return 0;
}

register

  • 通过register关键字修饰变量,向编译器发起建议(取决于是否满足条件),将变量放到寄存器中
1
register 数据类型 变量名 = 值;

extern

  • 通过extern关键字引入其他文件的变量和函数
1
2
3
4
5
6
int num;

void fn()
{
...
}
1
2
3
extern int num;

extern void fn();

类型限定符

  • 类型限定符和存储类型修饰符可以同时修饰同一变量,且没有顺序要求

const

  • 通过const关键字修饰的变量是只读的,被当作常量使用
1
const 数据类型 变量名 = 值;

volatile

  • 当变量被频繁改变时,编译器会自动优化,从寄存器中读写变量的值
  • 通过volatile关键字修饰变量,禁止编译器优化,强制访问内存
1
volatile 数据类型 变量名 = 值;

堆内存

  • 一个程序的栈大小是有限的,如果一个数组特别大时,会导致栈溢出,所以不要在栈里面定义太大的数组,此时可以使用堆来存数据
  • 如果一个数组的长度不能确定的时候,适合用堆而不是用栈存储数据

堆内存开辟空间

  • Windows堆内存最小页为4k
    • 如果用户在Windows上开辟1k空间,实际上Windows会开辟4k空间,将其中1k空间分给用户,用户再次开辟空间时,Windows不会再开辟新的空间,而是直接将剩余空间给用户使用,直到4k空间全部分配完毕,当用户仍然需要开辟空间时,Windows才会给用户开辟新的空间
    • Windows上这种开辟空间的方式,优点是效率更高,缺点是会浪费内存

开辟一块空间

  • 通过malloc()函数开辟堆内存空间

<num>:空间大小,单位字节

1
数据类型 *变量名 = malloc(<num>);

开辟多块空间

  • malloc()函数只负责开辟空间,需要通过memset()函数手动初始化(清空原始数据)

<num> * sizeof(int):一共开辟的空间大小

1
2
int *变量名 = malloc(<num> * sizeof(int));
memset(变量名, 0, <num> * sizeof(int));
  • calloc()函数不仅负责开辟空间,还会自动进行数据的初始化

<num>:开辟的空间个数
sizeof(int):每一块空间的大小

1
int *变量名 = calloc(<num>, sizeof(int));

自动变量

  • 如果存储为自动变量,在手动释放后仍然可以使用,但是释放后再次使用时,是一个新的堆空间,而且使用完仍然需要手动释放。释放的不是变量,而是指针指向的堆内的空间
1
auto 数据类型 *变量名 = malloc(<num>);

堆内存释放空间

  • 在堆中开辟的空间不会自动释放,需要手动释放
1
free(变量名);

堆内存重新分配开辟的空间

  • 使用malloc或calloc开辟的空间需要调整时,可以使用realloc
  • realloc开辟的空间也需要手动清除数据

变量名:想要修改空间的变量(返回的变量可以不相同,但通常设置为相同,因为旧的变量不需要自己维护)

1
变量名 = realloc(变量名, 新指定的内存大小);

完成