【笔记】C语言学习笔记

前言

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

include 关键字

  • 引入头文件

系统目录下的头文件

1
#include <头文件名.h>

当前目录下的头文件

1
#include "头文件名.h"

main函数

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

注释

行注释

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

块注释

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

变量命名规则

  • 可以包含数字字母下划线
  • 首字母不可以是数字
  • 不可以为c语言内置关键字
  • 向一个标准输出设备输出一个字符串

<str>:一个任意类型的数据

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

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

编译错误

warning

  • 警告,不影响编译

error

  • 错误,无法执行编译

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;
}

Linux上查看应该引入什么头文件

1
2
man 2 print
man 3 system

头文件存储位置

  • 在Linux平台下的头文件存储位置
1
vim /usr/include/stdio.h

解决跨平台

  • 在不同平台下,C语言只能保证源码语法相同,并不一定能保证执行结果相同
  • 为了解决跨平台问题,可以使用POSIX标准书写代码
    • Windows对POSIX的标准支持的比较差
  • 如果有符合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 常量类型 常量名 = 常量值

进制转换

十进制 二进制 八进制 十六进制
0 0 0 0
1 1 1 1
2 10 2 2
3 11 3 3
4 101 4 4
5 110 5 5
6 111 6 6
7 1000 7 7
8 1001 10 8
9 1010 11 9
10 1011 12 A
11 1100 13 B
12 1101 14 C
13 1110 15 D
14 1111 15 E
15 10000 17 F
16 10001 20 10
  • 1二进制位 = 1比特(bit)
  • 8比特 = 1字节(Byte)
  • 2字节 = 1字(Word)
  • 2字 = 1双字(DWord)
  • 1024字节 = 1千字(KByte)
  • 1024千字 = 1兆字(MByte)
  • 1024兆字 = 1GByte

转化二进制

  • 如果有这样一组二进制数
1
10101010111

快速二进制转八进制

  • 分成每3个位为一组,前面不足位用0补位
1
2
3
010 101 010 111

2 5 2 7
  • 所以转换后的八进制数为2527

快速二进制转十六进制

  • 分成每4个位为一组,前面不足位用0补位
1
2
3
0101 0101 0111

5 5 7
  • 所以转换后的十六进制数为557

无符号数和有符号数

无符号数

  • 无符号数只能表示正数
  • 范围:0~255

有符号数

  • 有符号数的第一位为符号位,可以表示负数
  • 范围:-125~124
原码
  • 第一位为符号位,其他位表示数
  • 正数符号位为0,负数符号位为1
1
2
3
4
5
+7的原码(1Byte)
0000 0111

-7的原码(1Byte)
1000 0111
反码
  • 如果为正数,原码和反码相同
  • 如果为负数,第一位为符号位,其他位的0和1相反
    • 正数符号位为0,负数符号位为1
1
2
3
4
5
+7的反码(1Byte)
0000 0111

-7的反码(1Byte)
1111 1000
补码
  • 如果为正数,反码和补码相同
  • 如果为负数,第一位为符号位,其他位为反码+1
    • 正数符号位为0,负数符号位为1
1
2
3
4
5
+7的补码(1Byte)
0000 0111

-7的补码(1Byte)
1111 1001

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>");

printf函数

  • 格式化输出
格式化 对应数据类型 描述
%d int 有符号十进制整数
%u unsigned int 无符号十进制整数
%o unsigned int 无符号八进制整数
%x%X unsigned int 无符号十六进制整数
%ld long 有符号十进制长整数
%lld long long 有符号十进制长整数
%lu long 无符号十进制长整数
%llu long long 无符号十进制长整数
%lo long 无符号八进制长整数
%llo long long 无符号八进制长整数
%lx%lX long 无符号十六进制长整数
%llx%llX long long 无符号十六进制长整数
%hd short int 短整数
%hu unsigned short int 无符号短整数
%f float或double 单精度浮点数或双精度浮点数
%e%E double 科学记数法
%c char 字符型
%s%S char* / 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
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语言的入栈规则

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

完成