C 语言(基础笔记)

重点-核心-本质

  • 内存
  • 进程
  • 线程

计算机基础

内存:外存与CPU的桥梁

(1)物理构成
内存条——元器件组成——0V(断电)或5V(通电)——0(表示断电)或1(表示通电)

即内存条由上亿个电子元器件组成,它们组成电路的电压有0V和5V两种:0V表示断电,用0表示;5V表示通电,用1表示。

1、 通过电路来控制这些元器件的通断电, 会得到很多 0、1 的组合。例如, 8 个元器件有 28=256 种不同的组合,
2、 1 个元器件称为 1 比特(Bit)1 位,8 个元器件称为 1 字节(Byte)

(2)内存的作用

程序在内存中运行(而不是在硬盘中)

对于读写速度, 内存 > 固态硬盘 > 机械硬盘
机械硬盘是靠电机带动盘片转动来读写数据的, 而内存条通过电路来读写数据,电机的转速肯定没有电的传输速度(几乎是光速)快。虽然固态硬盘也是通过电路来读写数据,但是因为与内存的控制方式不一样,速度也不及内存。

其过程如下:

  • 载入内存:加载器(Loader)将硬盘中的数据复制到内存,接着交给CPU来处理
  • 处理数据:CPU 直接从内存中读取数据,处理完成后将结果再写入内存。(如有需要,会将数据复制到硬盘进行保存)

(2)虚拟内存

当程序运行需要的空间大于内存容量时,会将内存中暂时不用的数据再写回硬盘;需要这些数据时再从硬盘中读取,并将另外一部分不用的数据写入硬盘。
这样,硬盘中就会有一部分空间用来存放内存中暂时不用的数据。这一部分空间就叫做虚拟内存(Virtual Memory)

扫描二维码关注公众号,回复: 13041161 查看本文章

在这里插入图片描述

数据储存:二进制和字符编码

(1)二进制
计算机要处理的信息是多种多样的,如数字、 文字、 符号、 图形、 音频、 视频等,,这些信息在人们的眼里是不同的。但对于计算机来说,它们在内存中都是一样的,都是以二进制的形式(0 和 1 序列)来表示。

(2)字符编码
将文字与二进制进行对应的规则就叫做字符编码:

  • ASCII编码:针对英文
    标准 ASCII 编码共收录了 128 个字符(空闲了一个比特位),其中包含了 33 个控制字符(具有某些特殊功能但是无法显示的字符)和 95 个可显示字符。
  • GB2312 编码GBK 编码:针对中文
  • Unicode编码全世界的文字

数据“交互”:编译链接

  • 编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。
  • 链接是把目标文件、操作系统的启动代码和用到的库文件进行组织形成最终生成可加载、可执行代码的过程

编译(compile)

编译:.c文件(源代码)——>目标文件/临时文件/中间文件(.obj文件-Visual C++或者.o文件-GCC)

C 语言的编译器有很多种,不同的平台下有不同的编译器:

  • Windows 下常用的是微软开发的 Visual C++,它被集成在 Visual Studio 中
  • Linux 下常用的是 GUN 组织开发的 GCC
  • Mac 下常用的是 LLVM/Clang,它被集成在 Xcode 中(Xcode 以前集成的是 GCC,来由于 GCC 的不配合才改为 LLVM/Clang,LLVM/Clang 的性能比 GCC 更加强大

链接(link)

链接其实就是一个“打包”的过程,它将所有二进制形式的目标文件(object file)和系统组件组合成一个可执行文件。
完成链接的过程也需要一个特殊的软件,叫做链接器(Linker)

链接:目标文件 + 系统组件 ——> 可执行文件

C语言主流编译器

如果不是特别强调,一般情况下我们所说的“编译器”实际上也包括了链接器。

C语言的编译器主要有两大块:桌面操作系统和嵌入式操作系统。

在这里插入图片描述

C语言程序

引子

(1)基础
头文件:.h文件(索引函数库)
main()函数:主函数,一个程序有且只有一个,是程序的入口函数
语句结束:必须使用 ;
返回值:int 型

(2)名词解释
puts = output string
printf = print format
scanf = scan format

(3)模板

#include<stdio.h> // include:复制头文件

int main()
{
    
    
    puts("C语言学习");
    return 0;
}

变量和数据类型

变量赋值在内存中找一块区域用来存放数据,并用变量名命名这个内存空间。(多次连续赋值给同一个变量,会导致之前的数据消失)

注意,C语言定义变量时,要指明数据的数据类型

数据类型除了指明数据的①解释方式,还指明了②数据的长度。-下面有讲)

注:C语言变量与Python变量的区别

  • C语言中,定义一个变量是将值放到变量盒子里面去;
    在这里插入图片描述
    赋值b=a,就是新建一个盒子,然后把值赋值一份放过去,特点是:两个值之间完全独立。

  • Python中,赋值变量就是标签的意思。
    在这里插入图片描述
    上面的 a=2 之后,1就没有归属了,就无法调用了,python的基于引用的内存管理器很快就会把这个对象的内存给清理掉的。

数据类型

概述

(1)数据类型
在这里插入图片描述
数据类型除了指明数据的解释方式,还指明了数据的长度

(2)数据类型长度

所谓数据长度(Length),是指数据占用多少个字节

  • 定义变量时还要指明数据的长度

  • 在 C 语言中,每一种数据类型所占用的字节数都是固定的,知道了数据类型,也就知道了数据的长度

    在这里插入图片描述

附: 静态类型语言、动态类型语言
要了解什么是动态语言,要首先了解“类型检查”。

静态类型语言,即在编译期间确定数据类型的语言。大多数静态类型语言是通过要求在使用任一变量之前声明其数据类型来保证这一点的。C、C++、Java、C#和Scala都是静态类型语言。

说大白话,意思就是动态类型语言可以直接进行变量赋值(在变量赋值之前不需要定义其变量类型),而静态类型语言必须先由人工定义变量类型,而后才能对其进行赋值。


强类型语言与弱类型语言

强类型语言: 一种总是强制类型定义的语言。 不管是在编译阶段还是运行阶段,一旦某种类型绑定到变量后,此变量便会持有此类型,并且不能同其他类型在计算表达式时,混合使用。
说大白话,强类型语言就是不能进行混合类型计算的语言,比如Python、Java、C#和Scala。

与之对应的是弱类型语言,弱类型语言容易与其他类型混合计算。弱类型语言代表有C、C++、PHP和JavaScript。

小结

数据是放在内存中的,在内存中存取数据要明确三件事情:

① 数据存储在哪里(变量名)
② 数据的长度(数据类型)
③ 数据的处理方式(数据类型)

所以,诸如 int n 这样的店形式就已经确定了数据在内存中的所有要素。

字符型和字符串

整数(short、int、long)

小数(float、double)

输入与输出

输出函数

  • puts():输出单个字符
  • putchar():输出单个字符
  • printf():输出各种类型的数据,可以替代前两个

输入函数

  • getchar()‘getche()’getch():用于输出单个字符
  • gets():获取一行数据,并作字符串处理
  • scanf():和printf类似,可以输入多种数据类型

printf()

printf()的格式控制符%[flag][width][.precision]type[]表示可有可无

举例:printf("d% d%, a, b");

  • type 是必须的,表示输出类型
  • width 表示最小输出宽度,单位是字符,比如%-9d表示结果输出最少占用9个字符的宽度。
  • .precision 表示输出精度,也就是小数的位数
  • flag是标志字符,比如%-9d中flags对应-

附:printf() 有一个尴尬的问题,就是有时候不能立即输出,这一切都是输出缓冲区(缓存)在作怪!
(先挖个坑!)

scanf()

scanf()的格式控制符(同printf一样):%[flag][width][.precision]type[]表示可有可无
举例:scanf("d% d%, &a, &b") // 两个%d之间用空格隔开
注意:scanf 的变量前要带一个&符号&称为取地址符,也就是获取变量在内存中的地址。

为什么输出printf不需要取址符而scanf必须用?
因为计算机需要将读入的值存放在变量中,所以先指定这个变量的地址(而printf输出的变量肯定是已经定义过的!)

但是一定记住有一个例外:int、char、float 等类型的变量用于 scanf() 时都要在前面添加&,而数组或者字符串用于 scanf() 时不用添加&,它们本身就会转换为地址。

运算符

(1)关系运算符

<<=>>===!=

(2)逻辑运算符

&&||!

循环和选择

if

if(判断条件){
    
    
    语句块1
}else{
    
    
语句块2
}

注:如果{}中只有一个语句块,则可以省略{}

除法运算:
对于除法运算,如果除数和被除数都是整数,那么运算结果也是整数,小数部分将被直接丢弃;如果除数和被除数其中有一个是小数,那么运算结果也是小数。

自动类型转换和强制类型转换

switch case 语句

switch 中case 的后面必须是一个整数,或者为整数的表达式,而不能包含任何变量。

switch(表达式){
    
    
    case 整型数值1: 语句1; 
    case 整型数值2: 语句2;
    ...
    case 整型数值n: 语句n;
    default: 语句 n+1;
    }

注:
1、break 是 C 语言中的一个关键字,专门用于跳出 switch 语句。
2、default 不是必须的。

条件运算符?:

条件运算符:表达式 1 ? 表达式 2 : 表达式 3

如果表达式 1 的值为真,则以表达式 2 的值作为整个条件表达式的值;否则以表达式 3 的值作为整个条件表达式的值。
条件表达式通常用于赋值语句之中。

可以认为条件运算符是一种简写的 if else,完全可以用 if else 来替换。

while 循环

while(表达式){
    
    
    语句块
}

do while 循环

do(){
    
    
语句块
    }while(表达式);

实际编程中使用 while 循环较多。

for 循环

for 循环比while循环更加灵活,完全可以取代while循环。

for(表达式1; 表达式2; 表达式3){
    
    
    语句块
    }
  • 表达式1:初始化语句,仅循环一次
  • 表达式2:循环条件,一般是关系表达式
  • 表达式3:自增或者自减操作的表达式

在这里插入图片描述
注:for 循环中的三个表达式都可以省略,但是中间的分号,不可省略。

  • 省略表达式1:for(; 表达式2; 表达式3),因为可以将初始条件放在for循环的外面
  • 省略表达式2:for(表达式1; ; 表达式3),很容易成为死循环
  • 省略表达式3:for(表达式1; 表达式2; ),因为可以将自增自减加入循环中
  • 省略三个表达式:for(; ; ),相当于while(1)语句

循环控制:break和continue

  • break 关键字:用于终止所有循环
  • continue语句:用于跳出本次循环(终止本次循环并继续后续循环)

数组array

一维数组

数组的定义:dataType arrayName[length];

注意:
1、数组中每个元素的数据类型必须相同
2、数组长度 length 最好是整数或者常量表达式

数组的特性:

数组内存是连续的

数组是一个整体,它的内存是连续的;也就是说,数组元素之间是相互挨着的,彼此之间没有一点点缝隙

连续的内存为指针操作(通过指针来访问数组元素)和内存处理(整块内存的复制、写入等)提供了便利,这使得数组可以作为缓存(临时存储数据的一块内存)使用。

二维数组

二维数组定义的一般形式:dataType arrayName[length1][length2];

字符数组/字符串

(1)定义
用来存放字符的数组称为字符数组
字符数组 = 字符串

字符数组实际上是一系列字符的集合,也就是字符串(String)。在 C 语言中,没有专门的字符串变量,没有 string 类型,通常就用一个字符数组来存放一个字符串。

(2)写法

a. 一个字符一个字符赋值
有以下写法:

  • char c[20] = {'T', 'o', 'd', 'a', 'y', 'i', 's', 'M', 'o', 'n', 'd', 'a', 'y'} // 注意这里指定的长度一定要比真实的字符数量至少要大1个(留一个给结束符)
  • char c[] = {'T', 'o', 'd', 'a', 'y', 'i', 's', 'M', 'o', 'n', 'd', 'a', 'y'} // 也可以不指定数组长度

b. 一次性赋值

C 语言规定,可以将字符串直接赋值给字符数组,有以下几种写法:

  • char str[17] = {"Today is Monday"};
  • char str[] = {"Today is Monday"}; // 也可以不指定数组长度
  • char str[] = "Today is Monday";// 简洁,实际开发中常用

注意:这种字符数组的整体赋值只能在字符数组初始化使用,不能用于字符数组的赋值,字符数组的赋值只能对其元素一一赋值

(3)定位
C语言字符串的定位(开头和结尾)

  • 字符串的开头:通过其名称定位就可以
  • 字符串的结尾:通过识别结束符'\0'

1、在 C 语言中,字符串总是以'\0'作为结尾,所以'\0'也被称为字符串结束标志,或者字符串结束符。

2、C 语言在处理字符串时,会从前往后逐个扫描字符,一旦遇到'\0'就认为到达了字符串的末尾,就结束处理。

" "包围的字符串会自动在末尾添加'\0',比如char str[7] = "abc123"
但是需要注意的是,逐个字符地给数组赋值并不会自动添加’\0’,比如char str[] = {'a', 'b', 'c'}

字符数组/字符串处理函数

sring.h 是一个专门用来处理字符串的头文件,比如下面一些主要的函数:

(1)strcat():字符串连接

  • strcat = string catenate
  • strcat(arrayName1, arrayName2);
  • 表示将arrayName2添加到arrayName1之后,并删除原来arrayName1后面的结束标志‘\0’。所以一定注意:arrayName1必须足够长!!

(2)strcpy():字符串复制(替换)

  • strcpy = string copy
  • strcpy(arrayName1, arrayName2);
  • 表示将arrayName2中的字符串拷贝到arrayName1中,包括其字符串结束标志‘\0’
    注意,原来的内容会被替换

(3)strcmp():字符串比较

  • strcmp = string compare
  • str(arrayName1, arrayName2);
  • strcmp() 以各个字符对应的 ASCII 码值,从头开始进行比较。若差值为0则再继续比较下个字符,若最终差值不为0则将差值返回。例如字符串"Ac""ba"比较则会返回字符"A"(65)'b'(98)的差值(-33)

函数

(1)函数定义

dataType functionName(){
    
    
    //body
}

需要记住,函数名不一定是main,但是系统从main函数开始执行,所以main函数只有一个。

注:返回类型不确定或者不需要返回值的函数可以用 void表示,比如 void hello()

强调一点,C语言不允许函数嵌套定义。

(2)关于返回值
1、return 语句可以有多个,可以出现在函数体的任意位置,但是每次调用函数只能有一个return语句被执行(执行后则立即返回,强制结束函数的执行),所以只有一个返回值

2、 return语句是提前结束函数的唯一办法,可以单独使用!比如,return;

(3)函数声明
在实际开发中,经常会在函数定义前就使用它们,这个时候就需要提前声明(Declaration)。

函数声明的格式:去掉函数定义中的函数体即可!

对于单个源文件的程序,通常是将函数定义放到 main() 的后面将函数声明放到 main() 的前面,这样就使得代码结构清晰明了,主次分明。

变量的作用域

所谓作用域(Scope),就是变量的有效范围。

变量的作用域由变量的定义位置决定, 在不同位置定义的变量, 它的作用域是不一样的。

  • 局部变量:在函数内部定义的变量,它的作用域也仅限于函数内部

  • 全局变量:在所有函数外部定义的变量,默认作用域是整个程序,也就是所有的代码文件

  • 块级变量:在代码块内部定义,作用域是代码块内部。比如在if判断、while和for循环中定义的变量。
    另外,C语言还允许出现单独的的代码块(仅仅由{}包围)!

    所谓“代码块”,就是由{}包围起来的代码。

理解函数

从整体上看,C 语言代码是由一个一个的函数构成的,除了定义说明类的语句(例如变量定义、宏定义、类型定义等)可以放在函数外面,所有具有运算或逻辑处理能力的语句(例如加减乘除、if else、for、函数调用等)都要放在函数内部

在所有的函数中,main() 是入口函数有且只能有一个,C 语言程序就是从这里开始运行的。

标准C语言共定义了15个头文件,称为“C标准库”:

  • 合格程序员:<stdio.h>、<ctype.h>、<stdlib.h>、<string.h>
  • 熟练程序员:<assert.h>、<limits.h>、<stddef.h>、<time.h>
  • 优秀程序员:<float.h>、<math.h>、<error.h>、<locale.h>、<setjmp.h>、<signal.h>、<stdarg.h>

main() 函数是主函数,它可以调用其它函数,而不允许被其它函数调用。因此,C 程序的执行总是从 main() 函数开始,完成对其它函数的调用后再返回到 main() 函数,最后由 main() 函数结束整个程序。

C语言预处理命令

使用库函数之前,应该用#include 引入对应的头文件。这种以#号开头的命令称为预处理命令

#include

#include 叫做文件包含命令,用来引入对应的头文件(.h 文件)

两种用法:

  • #include<stdHeader.h>:编译器到系统路径下查找头文件
  • #include"myHeader.h":编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找

也就是说,使用双引号比使用尖括号多了一个查找路径,它的功能更为强大。
推荐习惯是,使用尖括号来引入标准头文件,使用双引号来引入自定义头文件

#define

#define叫做宏定义命令,所谓宏定义,就是用一个标识符来表示一个字符串

一般形式:#define 宏名 字符串

注意,这里的字符串并不是指C语言中的“字符串”,而是指一般意义上的字符序列

带参宏定义(暂略!)
C语言条件编译(略)

指针

一切都是地址

  • 数据代码都放在内存
    C 语言用变量来存储数据用函数来定义一段可以重复使用的代码,它们最终都要放到内存中才能供 CPU 使用。

  • 操作系统会给不同的内存指定不同的权限,让计算机进行区分:

    • 代码:读取 + 执行
    • 数据:读取 + 写入
  • CPU 只能通过地址来取得内存中的代码和数据,程序在执行过程中会告知 CPU 要执行的代码以及要读写的数据的地址。
    编译和链接过程的一项重要任务就是找到这些名称所对应的地址

  • 需要注意的是,虽然变量名、函数名、字符串名和数组名在本质上是一样的,它们都是地址的助记符,但在编写代码的过程中, 我们认为变量名表示的是数据本身,而函数名、 字符串名数组名表示的是代码块或数据块的首地址
    (因此变量名需要通过加%来获取其地址,而函数名、数组名则不需要)

定义

(1)指针和指针变量:

  • 指针:数据在内存中的地址
  • 指针变量:储存了一份数据的指针的变量
    如果某个指针变量p储存的是变量c的地址,则我们称,p指向了c,或者说p是指向变量c的指针。

(2)定义指针:

  • dataType *name;

    • 注意①:定义指针时必须加上*,而赋值指针时则不再需要
    • 注意②:星号*的第二个用法——在指针变量前加*表示获取指针指向的数据
  • dataType *name = value;
    注意,这个value是一个内存地址(变量名的话需要加取地址符&

附:对星号*的总结

  • 表示乘法,例如 int a = 3, b = 5, c; c = a * b;,这是最容易理解的。
  • 表示定义一个指针变量,以和普通变量区分开,例如 int a = 100; int *p = &a;。
  • 表示获取指针指向的数据,是一种间接操作,例如 int a, b, *p = &a; *p = 100; b = *p;。

数组指针

数组(Array)是一系列具有相同类型的数据的集合,每一份数据叫做一个数组元素(Element)。

定义数组时,要给出数组名数组长度数组名可以认为是一个指针,它指向数组的第 0 个元素。在 C 语言中,我们将第 0 个元素的地址称为数组的首地址

但是注意:数组名和数组首地址并不总是等价!

数组指针定义:
如果一个指针指向了数组,则称其为数组指针(Array Pointer)

数组指针指向的是数组中的一个具体元素,而不是整个数组。

引入数组指针之后,具有了两种方式来访问数组元素:

  • 使用下标
  • 使用指针

附:两种形式的字符串

  1. 字符数组
  2. 直接使用一个指针指向字符串

它们最根本的区别是在内存中的存储区域不一样,字符数组存储在全局数据区或栈区,第二种形式的字符串存储在常量区。全局数据区和栈区的字符串(也包括其他数据)有读取和写入的权限,而常量区的字符串(也包括其他数据)只有读取权限,没有写入权限。

指针的应用

(暂略)

结构体

结构体

(1)why 结构体?
C 语言结构体 (Struct) 从本质上讲是一种自定义的数据类型,只不过这种数据类型比较复杂,是由 int、 char、 float 等基本类型组成的。可以认为结构体是一种聚合类型

实际开发中,我们可以将一组类型不同的、但是用来描述同一件事物的变量放到结构体中
(这不就是低层次“类”的概念么?)

(2)结构体定义

struct 结构体名{
    
    
结构体所包含的变量或数组
};

注意:别忘记最后面的分号

  • 一个例子:
struct stu{
    
    
    char *name; //姓名
    int num; //学号
    int age; // 年龄
    char group; //所在小组
    float score; // 成绩
};

其他形式或者用法:
(在定义结构体的同时还进行以下操作)

  • 定义结构体变量
  • 定义结构体变量的同时,省略结构体名(tag)
    注意:这两种操作仅限只使用定义的结构体变量,即这个结构体不可再复用!
    (详细可以参考程序员C语言快速上手——高级篇(九)

(3)成员获取和赋值

结构体的成员获取和赋值和Python的类类似,使用句点.结构体变量名.成员名

附:需要注意的是,结构体是一种自定义的数据类型,是创建变量的模板,不占用内存空间;结构体变量才包含了实实在在的数据,需要内存空间来存储

(4) 结构体数组
与定义结构体变量类似,将变量变为数组即可。

(5)结构体指针

当一个指针变量指向结构体时,我们就称它为结构体指针。
C 语言结构体指针的定义形式一般为:struct 结构体名 *变量名;

通过结构体指针可以获取结构体成员,一般形式为:(*pointer).memberName 或者pointer->memberName

一些补充

  • enum
    定义枚举类型:enum typeName{valueNmae1, valueName2, valueNmae3, ...};
    相比#define更加高效

  • union
    共用体定义格式:

    union 共用体名{
          
          
    成员列表
    }

    共用体也是一种自定义类型(与结构体很类似);

    结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。

  • typedef
    别名用法:typedef oldName newName;

typedef#define相似,其根本区别是typedef是一种彻底的“封装”类型,声明之后不能再往里面增加别的东西

  • const
    const 用来创建常量变量,即定义一个取值固定的变量:const type name = value;或者type const name = value

  • rand()
    rand()会随机生成一个位于 0 ~ RAND_MAX 之间的整数。

    C 语言标准并没有规定 RAND_MAX 的具体数值,只是规定它的值至少为 32767。在实际编程中,我们也不需要知道 RAND_MAX 的具体值,把它当做一个很大的数来对待即可。

    注意rand()产生的是伪随机数(每次计算机开机之后就不会再改变),

  • srand()
    还可以通过srand()函数来进行重新播种:srand((unsigned)time(NULL));,产生正真的随机数。

C语言文件操作

概述

在操作系统中,为了统一对各种硬件的操作,简化接口,不同的硬件设备也都被看成一个文件。 对这些文件的操作,等同于对磁盘上普通文件的操作。

操作文件的正确流程为:

  • 打开文件
  • 读写文件
  • 关闭文件

文件在进行读写操作之前要先打开,使用完毕要关闭。

打开文件,就是获取文件的有关信息,例如文件名、文件状态、当前读写位置等,这些信息会被保存到一个叫做FILE类型的结构体变量中;
关闭文件,就是断开与文件之间的联系,释放结构体变量,同时禁止再对该文件进行操作。

文件流的打开和关闭

(1)概念

  • 数据流(data stream):数据在数据源程序(内存)之间传递的过程。
    如果数据源为文件,则数据流则为文件流
  • 输入流(input stream):数据源 ——> 程序(内存)
  • 输出流(output steam):程序(内存)——>数据源

(2)打开文件

  • 打开文件:fopen()FILE *fopen(char *filename, char *modde);
  • fopen()的返回值FILE *fp = fopen("demo.txt", "r");
    如果希望接收 fopen() 的返回值,就需要定义一个 FILE 类型的指针

(3)最基本的文件打开方式
在这里插入图片描述在这里插入图片描述
(4)关闭文件

文件一旦使用完毕,应该用 fclose() 函数把文件关闭,以释放相关资源避免数据丢失

fclose()的用法:

int fclose(FILE *fp); // `fp`为文件指针

fclose(fp);

文件正常关闭时,fclose()返回0,如果返回非零值则表示有错误发生。

参考:

  1. Python学习之二:Python 与 C 区别
  2. python变量跟C中变量的区别

猜你喜欢

转载自blog.csdn.net/Robin_Pi/article/details/114366783