【笔记】C语言学习笔记

前言

C是一种通用的编程语言,广泛用于系统软件与应用软件的开发。于1969年至1973年间,为了移植与开发UNIX操作系统,由丹尼斯·里奇与肯·汤普逊,以B语言为基础,在贝尔实验室设计、开发出来。 C语言具有高效、灵活、功能丰富、表达力强和较高的可移植性等特点,在程序设计中备受青睐,成为最近25年使用最为广泛的编程语言。(维基百科

头文件

引入头文件

  • 通过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

入口函数

  • main函数是C语言程序的入口
  • 一个C语言程序必须有且只有一个main函数
1
2
3
4
5
int main()
{
...
return 0;
}

注释

行注释

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

块注释

  • 这种注释方式是C语言标准的注释方式
1
/* 块注释 */

函数返回值

  • 通过return关键字定义返回值
  • 如果定义函数时,返回值类型为void,就不需要返回值
  • 执行到return会立即结束当前函数,return后面的语句是没有意义的

main 函数的 return

  • 在main函数中调用return关键字,程序立即终止
  • return 0表示程序执行成功
  • return -1表示程序执行失败

System 函数

  • 调用system,可以再C语言的代码中执行另外一个程序

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

1
2
3
4
5
6
7
#include <stdlib.h>

int main()
{
system("<command>");
return 0;
}

解决跨平台

  • 在不同平台下,C语言只能保证源码语法相同,并不一定能保证执行结果相同
  • 为了解决跨平台问题,可以使用POSIX标准书写代码,尽量使用符合POSIX标准的函数
    • Windows对POSIX的标准支持的比较差

操作系统结构

用户模式

  • 普通的C语言程序,只能访问自己的内存

内核模式

  • 只有系统底层的C语言程序才可以访问系统内核相关的内存

CPU

32位和64位系统的区别

  • 32位CPU
    • 32位寄存器可以存放32个二进制位
    • 32位CPU的总线是32位
    • 32位管理内存最大值为4G
      • 所以在32位的系统上大于4G的内存是浪费的
      • 所以在小于等于4G内存的情况下,安装64位操作系统,并不一定比32位系统快
  • 如果在64位的CPU上安装64位操作系统,那么这个系统就是64位
1
2
0000 0000 0000 0000 0000 0000 0000 0000
1111 1111 1111 1111 1111 1111 1111 1111
  • 64位CPU
    • 64位寄存器可以存放64个二进制位
    • 64位CPU的总线是64位
    • 64位管理内存最大值至少大于4G
  • 如果在64位的CPU上安装32位操作系统,那么这个系统就是32位
1
2
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
  • 准64位CPU
    • 64位寄存器,总线是32位
    • 运算可以达到64位的效果,但是和外部交换数据时只能是32位的速度

RISC和CISC

  • RISC:精简指令集
    • SPARC
    • ARM
  • CISC:复杂指令集
    • x86

常量

定义常量

define

  • 这种定义常量的方法叫宏常量,宏常量的常量名通常用全大写
  • define通常紧跟include
  • C语言通常使用define定义常量
1
#define 常量名 常量值

const

  • const常量变量名可以小写
  • const通常写在代码内
  • C++通常使用const定义常量
1
const 常量类型 常量名 = 常量值

sizeof 关键字

  • 得到变量在内存中占用的大小
  • 返回一个无符号数
1
sizeof(变量)

大端对齐与小端对齐

  • 小端对齐(倒着放):高内存地址存放整数高位,低内存地址存放整数低位
    • x86、ARM的CPU通常使用的是小端对齐存储方式
  • 大端对齐(正着放):低内存地址存放整数高位,高内存地址存放整数低位
    • Unix的CPU通常使用的是大端对齐存储方式

数据类型

传送门

类型限定关键字

const 关键字

  • 不允许变量被修改,可以理解为常量
1
const 类型 变量名 = 变量值;

volatile 关键字

  • 当变量被频繁改变时,编译器会对代码自动优化,但这种优化有利有弊,为了防止编译器做优化,可以使用volatile关键字
1
volatile 类型 变量名 = 变量值;

register 关键字

  • 直接将变量放到(汇编的)寄存器中,而不是内存中
1
register 类型 变量名 = 变量值;
  • register是建议型指令,而不是命令型指令。如果CPU的寄存器有空闲,那么register就生效,如果没有CPU的寄存器没有空闲,那么register无效

字符串

  • 字符串在内存中是一段连续的char空间,以\0结尾

输出一个char

  • putchar一次只能输出一个字符

<char>:一个字符

1
putchar('<char>');

输出一个字符串

<str>:一个字符串

1
printf("<str>");

输出语句

格式化输出

1
2
3
4
5
6
7
#include <stdio.h>

int main()
{
printf("%d", 0);
return 0;
}
格式说明符 数据类型 描述
%hd short int 短整型数据
%hu unsigned short int 无符号短整型数据
%d 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* 十六进制指针数据
%% - 输出一个百分号

指定数据长度

用空格补齐

  • %后加数字,表示输出的结果最小长度,如果不足,用空格补齐
    • 如果为正数,空格在数据前补齐,数据右对齐
    • 如果为负数,空格在数据后补齐,数据左对齐

用0补齐

  • 在用空格补齐的基础上,再在%后加0,表示用0补齐至最小长度
    • 如果最小长度为负数,不可以使用0补齐
      • 如果仍然使用,编译时警告,0将被忽略

getchar函数

  • 接收一个字符,获取它的ASCII码值
1
2
3
#include <stdio.h>

int 变量 = getchar();

scanf函数

  • 接收一个变量

&变量名:这个参数需要指定内存地址

1
scanf("%d", &变量名);

VS解决报错

  • 在VS2013中,VS认为scanf不安全,所以使用scanf会报错
  • 解决办法有两种
    • 一种是添加一个宏函数(这个宏函数一定要写到文件的第一行)
    • 一种是屏蔽警告
1
#define _CRT_SECURE_NO_WARNINGS
1
#pragma warning(disable:4996)

运算符

传送门

分支语句

传送门

循环语句

传送门

exit函数

  • 在程序的任何时候调用exit函数,程序立即终止
1
2
3
#include <stdlib.h>

exit(0);

作用域

1
2
3
4
5
6
7
8
9
10
11
int a = 0; // 文件作用域

int main() {

int a = 0; // 函数作用域

{
int a = 0; // 代码块作用域
}

}

自动变量

  • 代码块内部定义的都是自动变量,只不过平常我们会省略auto关键字
1
2
3
{
auto int a = 0;
}

寄存器变量

1
register int a = 0;

静态变量

  • 静态变量放在代码块内或代码块外都可以
  • 静态变量只初始化一次,在程序加载到内存的时候就创建,直到程序运行结束才销毁
1
statis int a = 0;

静态全局变量

  • 静态全局变量只能在定义它的文件内使用

静态全局函数

  • 静态全局函数只能在定义它的文件内使用

对堆内存的操作

引入头文件

1
#include <stdlib.h>

返回值为一个指针

  • 返回一个指针时,应该使用堆内存来操作,因为栈的生命周期只有代码块内,当代码块执行结束后,指针指向的地址就没有意义了
1
2
3
4
5
6
7
8
9
10
11
12
13
int *a()
{
int a = 0;
return &a
}

int main()
{
int *p = a();
...
free(p);
return 0;
}

C语言的入栈规则

  • 函数的形参是从右到左入栈的

完成