根据以下学习视频,个人整理的笔记
https://www.bilibili.com/video/BV1jS4y1x7Gf/?spm_id_from=333.999.0.0&vd_source=7a8946d22777450e46486d5fd60d8d4d
计算机硬件架构
冯诺依曼架构
现在主板上的北桥芯片已经被去掉了,但是基本的联络架构还是如下所示
CPU的指令架构:
- 复杂指令集
- 简单指令集
- 并行指令架构
CPU里面有一个小的存储临时数据的单元,叫寄存器。如果寄存器不够用就会用到内存
利用半导体晶体管模拟设计1bit内存单元
内存存储信息基本是用逻辑电路来实现的
进制
一位16进制数能直观表达四位2进制数
一位8进制数能直观表达三位2进制数
我们在一般比较关心16进制和2进制之间的关系
内存
内存单元的基本单位是bit
内存操作的最小单元是字节
字节是计算机可以单独读写的最小单元
8bit = 1Byte
1024B = 1KB
1024KB = 1MB
1024MB = 1GB
CPU从内存中读写数据
我们常说的32位或者64位操作系统,其实就是指地址总线的数量
C语言简介
-
C语言标准历程
-
C语言关键字的更新
C语言追求的是:自由!性能!
使用C语言一定要骚起来!
入门第一个程序
- 新建一个项目
解决方案的名称相当于整个完整项目的名称
一个解决方案可以包含多个项目,一个项目中可以包含多个源文件
- 编写代码
#include<stdio.h>
int main(void) {
printf("一分钟后你的电脑将被关闭!");
system("shutdown /s");
//system("shutdown /a");// 这个是取消关机
return 0;
}
编译器的工作流程
我们人通过编程语言和编译器沟通,编译器和计算机沟通
编译的两种形式:
- 编译(性能高,速度快)
- 解释(可以跨平台,不同的平台装上同一个解释器即可)
编译器工作的原理图
C/C++的编译器:
- Gcc
- Clang
- BC++
- VC6.0
- Visual Studio 2022
常量和变量
常量和变量全部保存在内存之中!
变量的声明本质就是内存的分配
自然数保存在内存中
#include <stdio.h>
int main(void) {
unsigned MSalary = 0, WSalary = 0;
unsigned ASalary = 0, YSalary = 0;
printf("请输入爸爸的工资:\n");
scanf_s("%u", &MSalary);
printf("请输入妈妈的工资:\n");
scanf_s("%u", &WSalary);
ASalary = MSalary + WSalary;
ASalary = ASalary / 2;
YSalary = (MSalary + WSalary) * 12;
printf("爸爸工资:%u,妈妈工资:%u,家庭人均收入为:%u\n", MSalary, WSalary, ASalary);
printf("家庭年收入为一共为:%u", YSalary);
return 0;
}
1111 1111
+ 0000 0001
-------------
1 0000 0000 自然数MAX+1,这里存在溢出
自然数数据类型性质:
- MAX + 1 = 0
- 0 - 1 = MAX
负整数保存在内存中
0表示正,1表示负
我们明确表示正1为:0 000 0001
我们知道正1加上负1为0,其实本质上等于0是利用了溢出的效果
从计算机的角度出发,我们该如何表示负1?
0 000 0001 这个表示正1
+ ? ??? ???? 我们要如何表示负1
------------
1 0 000 0000 利用溢出的效果表示0
0 000 0001 这个表示正1
+ 1 111 1111 这个表示负1
------------
1 0 000 0000 利用溢出的效果表示0
同理可得
0 111 1111 这个表示正127
+ 1 000 0001 这个表示负127
------------
1 0 000 0000 利用溢出的效果表示0
那么这个1 000 0000表示什么???这个我们表示为负128
1 000 0000
+ 1 000 0000
------------
1 0 000 0000
得出结论:最高位是1其它全是0的数全部不能作求负运算,作求负运算需要往上升一位
已知X,求-X的推导过程基本如下
有符号整数数据类型
有符号整数的数学性质(我们可以用数轴来直观表达这个数学性质):
- MAX + 1 = 最小值
- 最小值 - 1 = MAX
- 负的最小值等于最小值本身(比如说:-(-128)等于-128)
比如说
0 111 1111 127
+ 0 000 0001 1
------------
1 000 0000 -128
二进制、八进制、十六进制的表达
- 二进制:前缀 0b (不是C语言标准里面支持的操作,跟编译器有关,是编译器支持的操作)
- 八进制:前缀 0
- 十六进制:前缀 0x
#include <stdio.h>
int main(void) {
unsigned char value1 = 0b11111111;
unsigned char value2 = 0xFF;
// %hh 表示限定1个字节
printf("%hhd %hhu\n", value1, value1);// -1 255
printf("%hhd %hhu\n", value2, value2);// -1 255
unsigned short vshort = value1;
// %h 表示限定2个字节
printf("vshort:%hd %hu\n", vshort, vshort);// 255 255
return 0;
}
小数在内存中的储存
定点数:小数点的位置是固定的
定点数的优点:存储方式简单,精度准确
定点数的缺点:编码复杂,浪费空间
浮点数:小数点的位置是浮动的
IEEE754:
举个例子,0.5在内存中的存储表示(0.5的二进制是0.1,二进制0.1的科学计数法为1E-1):
设计一个BMI指数计算器
#include <stdio.h>
int main(void) {
float weight = 0, height = 0;
float bmi = 0;
printf("请输入体重(单位kg):\n");
scanf_s("%f", &weight);
printf("请输入身高(单位m):\n");
scanf_s("%f", &height);
bmi = weight / (height * height);
printf("你的BMI为 [%f]\n", bmi);
printf("18.4以下 [消瘦]\n");
printf("18.5-23.9 [正常]\n");
printf("24-27.9 [超重]\n");
printf("28以上 [肥胖]\n");
return 0;
}
常量分配内存的依据
我们可以在调试的模式下,查看反汇编的信息,可以知道常量也是会分配内存的!
常量的内存分配大小的依据是什么?
依据也是常量的类型
运算符
- =
-
-
-
- /
- %
- -(这个是取反)
- sizeof()
- ++
简单的汇编指令如下
类型转换
表达式中类型转换规则:
- char short 会升级为 int 有些时候会升级为 unsigned int
- 两种类型及以上的运算,会升级为最高级的类型
- 赋值运算会造成,类型升级或者类型降级
printf函数
英文在内存中的存储
ASCII
scanf函数
单字符输入输出
位运算
- 与运算 &
- 或运算 |
- 异或运算 ^ (本质是表达两个数的差异性)
- 左移运算符 << (左移其实相当于乘法)
- 右移运算符 >> (右移其实相当于除法)
关系运算符
- >
- <
- ==
- >=
- <=
- !=
- if
- if…else…
在某些情况下我们可以利用数学来替代if语句
逻辑运算符
三元运算符
- 条件?成立的结果:不成立的结果
通过短路原则,把容易计算的表达式写在前面,能够减少CPU的运算,提升性能!
异或运算在某些判断情况下也能提升性能
switch
#include<stdio.h>
int main(void)
{
unsigned age;
printf("Please input your number(type integer):\n");
scanf_s("%3d", &age);
switch (age)
{
case 40:
printf("恭喜!中奖了,四十岁大礼包!\n");
break;
case 50:
printf("恭喜!中奖了,五十岁大礼包!\n");
break;
case 70:
printf("恭喜!中奖了,七十岁大礼包!\n");
break;
default:
printf("没有礼包!");
break;
}
}
switch通过逆向分析底层实现会发现:如果case的值有规律,则会生成一个分支跳转表,这样的话会比使用if语句实现的性能更高
运算符优先级
while
#include <stdio.h>
int main(void) {
unsigned Pswd = 0;
unsigned PswdConfirm = 0;
unsigned CrackPass = 0;
while (1) {
printf("请输入密码:\n");
scanf_s("%u", &Pswd);
printf("请再次输入密码:\n");
scanf_s("%u", &PswdConfirm);
if (Pswd == PswdConfirm) {
if (Pswd < 1000000) {
break;
}
else {
printf("密码长度过长!请重新输入!\n");
continue;
}
}
else {
printf("两次输入的密码不同!请重新输入!\n");
}
}
printf("密码设置成功,密码为:%06u", Pswd);
while (CrackPass != Pswd) {
printf("-------->>>>>>>正在尝试破解密码:%06u\n", CrackPass);
CrackPass++;
}
printf("设置的密码已经被破解!密码为:%06u", CrackPass);
return 0;
}
do-while
#include <stdio.h>
int main(void) {
char input;
unsigned year, month = 1, days, tmpDays;
do {
printf("日历打印程序\n");
printf("请输入要打印日历的年份:\n");
scanf_s("%u", &year);
month = 1;
do {
printf("\n------------------\n");
printf(" %4u 年 %2u 月 \n", year, month);
printf("\n------------------\n");
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
days = 31;
break;
case 4:
case 6:
case 9:
case 11:
days = 30;
break;
case 2:
days = 28 + ((year % 100) && (year % 4 == 0) || (year % 400 == 0));
}
tmpDays = 1;
do {
printf("%4u", tmpDays);
if (tmpDays % 7 == 0) {
printf("\n");
}
} while (tmpDays++ < days);
} while (month++ < 12);
printf("\n输入Y或y继续打印其它年份的日历!\n");
getchar();// 过滤掉回车
input = getchar();
getchar();// 过滤掉回车
} while (input == 'Y' || input == 'y');
return 0;
}
彩票小程序
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <Windows.h>
int main(void)
{
unsigned Money = 10000;
unsigned Price1 = 150;
unsigned Price2 = 500000;
unsigned choice = 0;
unsigned WinPrice;
_Bool Win;
unsigned Number,UserNumber;
srand(time(0));// 初始化随机数的队列
do {
system("cls");
printf("======欢迎来到彩票店======");
printf("您当前的余额为[%u]\r\n", Money);
printf("请选择您要参与的玩法:\r\n");
printf("[ 1 ]猜单双\r\n");
printf("[ 2 ]猜数字\r\n");
scanf("%u", &choice);
switch(choice)
{
case 1:
printf("正在进行猜单双的玩法\r\n");
printf("请输入要投注的数字\r\n");
printf("[0] 表示偶数");
printf("[1] 表示奇数");
scanf("%u", &UserNumber);
WinPrice = Price1;
break;
case 2:
printf("正在进行猜数字的玩法\r\n");
printf("请输入要投注的数字\r\n");
scanf("%u", &UserNumber);
WinPrice = Price2;
break;
default:
printf("当前玩法没有开通,请重新输入!\r\n");
system("pause");
continue;
}
Money = Money - 100;
Win = 0;
Number = rand();// 产生一个随机数
printf("开奖结果为 [%u]\r\n", Number);
switch(choice)
{
case 1:
Win = Number%2 == UserNumber;
break;
case 2:
Win = Number == UserNumber;
break;
}
if (Win)
{
Money = Money + WinPrice;
printf("恭喜您中奖了,中奖金额为[%u]", WinPrice);
}
else printf("很遗憾,未中奖。");
system("pause");
} while(1);
return 1;
}
利用循环预测彩票小程序下次开奖的结果
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <Windows.h>
int main(void)
{
unsigned ends0, ends1, ends2, ends3, ends4;
unsigned _ends0, _ends1, _ends2, _ends3, _ends4;
unsigned srandV;
unsigned rights = 0, i = 0;
unsigned Number;
printf("请输入五次开奖结果,用逗号分割:\n\r");
scanf("%u,%u,%u,%u,%u", &ends0, &ends1, &ends2, &ends3, &ends4);// 通过已有的开奖结果预测下次开奖的结果
srandV = time(0);
while (srandV--)
{
srand(srandV);
_ends0 = rand();
_ends1 = rand();
_ends2 = rand();
_ends3 = rand();
_ends4 = rand();
if ((_ends0 == ends0) && (_ends1 == ends1) && (_ends2 == ends2) && (_ends3 == ends3) && (_ends4 == ends4))
{
printf("匹配到了关键的srand-key:%u",srandV);
break;
}
}
while (1)
{
Number = rand();
printf("下一次的开奖结果为:[%u]\r\n", Number);
system("pause");
}
return 1;
}
for循环
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
unsigned iNumber,iDiv;
unsigned isP;
for (iNumber = 3; iNumber < 1000; iNumber=iNumber+2)// 偶数没必要看了,肯定不是质数
{
isP = 0;
for (iDiv = 3; iDiv < iNumber; iDiv++)
{
if (iNumber % iDiv == 0)
{
isP = 1;
break;
}
}
if (!isP)
{
printf("[%u]是一个质数\r\n", iNumber);
}
}
return 1;
}
要学会去优化代码,这个思维很重要!!!
优化代码等于优化性能!!!
性能才是C语言的灵魂
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
const unsigned Money = 10000;
const unsigned aPr = 500;
const unsigned bPr = 300;
const unsigned cPr = 100;
unsigned aNum, bNum, cNum;
unsigned aMax, bMax;
aMax = Money / aPr;
bMax = Money / bPr;
for (aNum = 0; aNum <= aMax; aNum++)
{
for (bNum = 0; bNum <= bMax; bNum++)
{
cNum = 100 - aNum - bNum;
if((cNum % 3 == 0) && (aPr * aNum + bPr * bNum + cPr * cNum / 3 == Money))
{
printf("解决方案是A商品采购[%u]个,B商品采购[%u]个,C商品采购[%u]个\r\n", aNum, bNum, cNum);
}
}
}
return 1;
}
利用数学优化代码,从而优化性能!
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
unsigned aNum, bNum, cNum;
unsigned t;
for (t = 0; t <= 3; t++)
{
aNum = 4 * t;
bNum = 25 - 7 * t;
cNum = 75 + 3 * t;
printf("解决方案是A商品采购[%u]个,B商品采购[%u]个,C商品采购[%u]个\r\n", aNum, bNum, cNum);
}
return 1;
}
goto语句
- goto语句能够使得程序跳转到指定位置执行
- goto语句由两部分组成
- 标签(先定义一个标签)
- goto 标签(代码会跳转到标签的位置)
- 标签的定义规则与标识符定义规则相同
不同的循环使用场景
数组
-
数组在内存中是连续的内存空间,为了清楚内存中遗留的数据,在使用数组前应当对其进行初始化
int Salve[4] = {100,200,300,400};// 全部初始化 int Salve[4] = {[3]=400};// 指定数组下标初始化,数组其它下标的数据均为0 int Salve[] = {[9]=400};// 这时数组默认的长度为10
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
const int Max = 20;
int main(void)
{
unsigned salev[100];// 数组的长度是100
unsigned count = 0;
unsigned all = 0;
char input;
do
{
printf("请输入[%u]号员工的销售额:\r\n", count);
scanf("%u", &salev[count]);// count的取值范围为0-99
all = all + salev[count];
count++;
printf("输入[Y/y]继续输入 ");
// 把回车消掉
getchar();
input = getchar();
getchar();
} while (input == 'y' || input == 'Y');
all /= count;
printf("一共输入了[%u]个员工的业绩,平均业绩是[%u]", count, all);
return 1;
}
数组的应用
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
unsigned days[] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
unsigned month, year;
printf("请输入要查询的年份:\r\n");
scanf_s("%u", &year);
days[1] += (year % 100) && (year % 4 == 0) || (year % 400 == 0);
for (month = 1; month < 13; month++)
{
printf("%u 年 %2u 月 有 %2u 天\r\n", year, month, days[month - 1]);
}
return 1;
}
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
int PrNumber[300] = { 3 };
int Count = 1;
int iPr = 1;
for (int i = 5; i < 1000; i += 2)
{
iPr = 1;
for (int y = 0; y < Count; y++)
{
if (i % PrNumber[y] == 0)
{
iPr = 0;
break;
}
}
if (iPr)
{
PrNumber[Count] = i;
Count++;
printf("%u \r\n", i);
}
}
return 1;
}
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
unsigned count = 0;
unsigned studentId[1000], studentRecord[1000];
char input = 0;
unsigned i,sort,tmp;
do
{
sameId:
printf("请输入 学号:成绩 (例如:20220001:56)\r\n");
scanf("%u:%u", &studentId[count], &studentRecord[count]);
for (i = 0; i < count; i++)
{
if (studentId[count] == studentId[i])
{
printf("学号为 [%u] 的成绩已经存在,请继续输入其它学生", studentId[i]);
goto sameId;
}
}
count++;
printf("输入[Y/y]继续输入 \r\n");
getchar();
input = getchar();
getchar();
} while (input == 'Y' || input == 'y');
for (i = 0; i < count; i++)
{
printf("学号 [%u] 成绩 [%u] \r\n", studentId[i], studentRecord[i]);
}
for (i = 0; i < count - 1; i++)// 判断循环几次
{
for (sort = 0; sort < count - 1 - i; sort++)// 每多循环一次就多减少一次比较
{
if (studentRecord[sort] < studentRecord[sort +1])
{
tmp = studentRecord[sort];
studentRecord[sort] = studentRecord[sort + 1];
studentRecord[sort + 1] = tmp;
tmp = studentId[sort];
studentId[sort] = studentId[sort + 1];
studentId[sort + 1] = tmp;
}
}
}
for (i = 0; i < count; i++)
{
printf("学号 [%u] 成绩 [%u] \r\n", studentId[i], studentRecord[i]);
}
return 1;
}
查看数组在内存中的存储
-
先编写代码
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main(void) { int ary[] = { 1,2,3,4,5,6 };// 在这一行标记断点 printf("%d", ary[0]); return 1; }
-
使用调试模式运行
-
打开内存查看
-
查看数组在内存中数据的存储情况
-
程序执行到下一步,查看数组在内存中数据的存储情况
关于数组的越界访问
- 除非你清楚越界访问的后果,否则不应该使用越界访问