C语言(已停止更新)

根据以下学习视频,个人整理的笔记

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;
    }
    
  • 使用调试模式运行

    在这里插入图片描述

  • 打开内存查看

    在这里插入图片描述

  • 查看数组在内存中数据的存储情况

    在这里插入图片描述

  • 程序执行到下一步,查看数组在内存中数据的存储情况

    在这里插入图片描述

关于数组的越界访问

  • 除非你清楚越界访问的后果,否则不应该使用越界访问
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值