郝斌老师 c语言学习笔记

文章目录

c语言概述

如何看懂一个程序

​ 1.流程
​ 2.每个语句的功能
​ 3.试数

课程计划
image-20220603153216655

举例子,一元二次函数

#include <stdio.h>
#include <math.h>

int main(void)
{
	//把三个系数保存到计算机中
	int a = 1;
	int b = 2;
	int c = 3;
	double delta; //delta存放的是b*b - 4*a*c
	double x1; //存放一元二次方程的其中一个解 
	double x2; //存放一元二次方程的其中一个解 
	
	delta = b*b - 4*a*c;
	
	if(delta > 0)
	{
		x1 = (-b + sqrt(delta)) / 2*a;
		x2 = (-b - sqrt(delta)) / 2*a;
		printf("该一元二次方程有两个解,x1 = %f,x2 = %f",x1,x2);
	} 
	 
	else if(delta =  0)
	{
		x1 = (-b)/2*a;
		x2 = x1; //把右边赋给左边
		printf("该一元二次方程有唯一一个解,x1 = x2 = %f",x1); 
	}
	else
	{
		printf("无解");
	}
	return 0;
}

C编程预备计算机专业知识

  1. cpu 内存条 硬盘 显卡 主板 显示器 之间的关系
    如播放一个视频,操作系统在cpu的控制下将硬盘中的视频数据读到内存条中,在内存中进行处理,(因为cpu不能直接处理硬盘上的数据)之后才能被cpu执行,cpu将处理的图片数据送到显卡处理再输在显示器上,cpu控制将处理得到的声音送到声卡处理,再送到喇叭上发出声音。而主板是将各设备连接起来。

  2. HelloWord程序是如何运行起来的
    编译软件通过编译和链接后生成.exe文件,然后编译软件对操作系统发出请求运行该.exe文件,操作系统调用cpu来执行。

  3. 什么是数据类型
    基本类型数据
    整数
    整形 – int – 4
    短整形 – short int – 2
    长整形 – long int – 8
    浮点型
    单精度 – float – 4
    双精度 – double – 8
    字符型
    char – 1
    一般来说习惯上用n,m,i,j,k 等表示int 类型的变量;c,ch 等表示字符类型变量;a 等表示数组;p 等表示指针。当然这仅仅是一般习惯,除了i,j,k 等可以用来表示循环变量外,别的字符变量名尽量不要使用。

    #include <stdio.h>
    
    int main(void)
    {
    	int i = 10.6;
    	printf("i = %d\n",i);
    	
    	char c = 'm';
    	printf("c = %c\n",c);
    }
    

    ​ 复合类型数据
    ​ 结构体
    ​ 枚举
    ​ 共用体(已淘汰)

  4. 什么是变量
    变量的本质就是内存中一段存储空间
    在定义一个变量时,就是在内存中给此变量分配一个存储空间,给变量赋值就是在分配的存储空间中存放相应的数据。当变量使用完毕后,内存空间将被释放。但内存空间中的垃圾数据并没有被清除,操作系统通过0和1标志来区分此空间是否被使用,这样内存的处理速度才更快。
    定义和声明的区别:定义分配内存,而声明则没有。定义只能出现一次,而声明可以出现多次

  5. cpu 内存条 编译软件 操作系统之间的关系
    同上2

  6. 变量为什么必须得初始化(重点!)
    所谓初始化就是赋值的意思。
    如果不给变量初始化的话,系统会默认给变量一个很大初始值(有些编译软件会给未初始化的变量赋值为0),该初始值是上一个软件使用该内存后遗留下来的垃圾值(也叫填充字)(硬件中存储的都是0 1 组合的代码)。来提醒程序员该变量未初始化。
    补充内容:当运行一个软件时,操作系统将硬盘中的数据拷贝到内存条中;在使用该软件的时候,操作系统不在将此段内存空间分配给其它软件;当操作系统运行完毕后操作系统将回收该内存空间,(但是!操作系统并不会清空该内存空间遗留下来的数据>)(操作系统中有一个系统分配表,如果该空间被使用了就写个1,没被使用就写个0,当系统回收该内存空间时,即把1改成0就好,一个一个改写内存空间的话太浪费时间了),以便在此分配给其他软件使用。
    综上所述,一个软件所分配到的空间中极可能存在着以前其它软件使用过后的残留数据,这些数据被称之为垃圾数据。所以通常情况下我们为一个变量,一个数组分配好内存空间后都要对该内存空间初始化!

  7. 如何定义变量
    数据类型 变量名 = 要赋的值;
    等价于
    数据类型 变量名;
    变量名 = 要赋的值;
    e.g:
    int i = 3; 等价于 int i; i = 3;

    int i, j;

    等价于 int

    i; int j;

    int i , j = 3; 等价于 int i; int j; j = 3;

    int i = 3, j = 5; 等价于 int i; int j; i =3; j = 5;

    int i, j; i = j = 5; 等价于 int i, j; i = 5; j = 5;

  8. 什么是进制
    几进制就是逢几进一
    C语言规定八进制前要加0(注意是零不是o),十六进制前要加0x或者0X,十进制前什么都不加!image-20220604180157383
    image-20220604181916444

  9. 常量在C语言中是如何表示的image-20220604210313733
    整数
    十进制: 传统的写法
    十六进制: 初始化时前面加0x或0X
    八进制: 初始化时前面加0 (注意是零)
    浮点数
    传统的写法
    float x = 3.2 ; //传统
    科学计数法
    float x = 3.2e3; //表示 x = 3200
    float x = 123.45e-2 //表示 x = 1.2345
    字符型
    单个字符用单引号括起来
    ‘A’表示字符A

    ​ ‘AB’ 错误

    ​ "AB"正确
    ​ 字符串用双引号括起来
    ​ "A"正确,因为"A"表示’A’和’\0’的组合image-20220604210445717

  10. 常量以什么样的二进制代码存储在计算机中
    整数是以补码的形式转换为二进制代码存储在计算机中的
    实数是以IEEE754标准转换为二进制代码存储在计算机中的
    字符的本质实际上也是与整数的存储方式相同
    1、计算机在任何情况下都只能识别二进制
    2、计算机在底层存储数据的时候,一律存储的是“二进制的补码形式”计算机采用补码形式存储数据的原因是:补码形式效率最高。
    3、什么是补码呢?
    实际上是这样的,二进制有:原码 反码 补码
    注:一个二进制数,首位0表示该数是正数,首位是1表示该数是负数。
    二、正文
    对于一个正数来说:二进制原码、反码、补码是同一个,完全相同。

    int i = 1;
    

    对应的二进制原码:00000000 00000000 00000000 00000001
    对应的二进制反码:00000000 00000000 00000000 00000001
    对应的二进制补码:00000000 00000000 00000000 00000001
    对于一个负数来说:二进制原码、反码、补码是什么关系呢?

    byte i = -1;
    

    对应的二进制原码:10000001
    对应的二进制反码:11111110(符号位不变,其它位取反)
    对应的二进制补码:11111111(反码+1)
    eg.分析 byte b = (byte)150;------>这个b是多少?

    int类型的4个字节的150的二进制码是什么?
    00000000 00000000 00000000 10010110
    
    将以上的int类型强制类型转为1个字节的byte,最终在计算机中的二进制码是:  10010110
    
    千万要注意:计算机永远存储的都是二进制补码形式。也就是说上面
    
    10010110 这个是一个二进制补码形式,你可以采用逆推导的方式推算出
    这个二进制补码对应的原码是啥:
     10010110 ---> 二进制补码形式(反码-1)⬇
     10010101 ---> 二进制反码形式(符号位不变,其它位取反)⬇
     11101010 ---> 二进制原码形式 
    
  11. 代码规范化
    代码可读性更强【容易让自己和别人更清楚地看懂程序】
    使程序更不容易出错

  12. 什么是字节
    字节就是存储数据的单位,并且是硬件能够访问的最小单位
    1字节 = 8位
    1K = 1024字节
    1M = 1024K
    1T = 1024M

  13. 不同类型数据之间相互赋值的问题
    暂不考虑

    int i = 45: 
    long j = 102345; 
    i = j; 
    printf("%ld %d\n",i,j);
    float x = 6.6; 
    double y = 8.8; 
    printf("%f %lf\n", x, y);
    
    1. 什么是ASCII
      ASCII不是个值,而是一种《规定》
      ASCII规定了不同的字符是使用哪个整数值去表示
      它规定了
      ‘A’ – 65
      B ’ – 66
      a’ - - 97
      ‘b’ – 98
      ‘0’ – 48
  14. 字符的存储[字符本质上与整数的存储方式相同]

基本的输入输出函数的用法

printf() —— 将变量的内容输出到显示器上

​ 四种用法
​ 1.printf(“字符串”);

​ 2.printf(“输出控制符”,输出参数);

​ 3.printf(“输出控制符1 输出控制符2 。。。”,输出参数1,输出参数2.。。。)

​ 4.printf(“输出控制符,非输出控制符”,输出参数) ;

输出控制符输出参数类型
%dint
%ldlong int
%cchar
%ffloat
%lfdouble
%x(或者%X或者%#X)int 或 long int 或 short int
%0同上
%s字符串
输出十进制:%d
输出十六进制:%x
输出单个字符:%c
输出字符串:%s
输出变量所在的地址:%p
#include <stdio.h>

int main(void)
{
//	1.printf("字符串");
	printf("哈哈"); 			//\n表示换行 
	
//	2.printf("输出控制符",输出参数);
	int i = 10;
	printf("%X",i);			//输出控制符是用来确定把二进制代码到底以什么样的形式进行输出的 
	
//	 3.printf("输出控制符1 输出控制符2 。。。",输出参数1,输出参数2.。。。)
	int j = 3;
	int k = 5;
	printf("%d %d",j,k); 
	
//	4.printf("输出控制符,非输出控制符",输出参数) ; 
	int m = 50;
	printf("%#X\n",m);
	
	return 0;
}

为什么需要输出控制符

  1. 二进制代码 0 1 组成的代码可以表示数据也可以表示指令

  2. 如果01组成的代码表示的是数据的话,那么同样的01代码组合以不同的输出格式输出就会有不同的输出结果

scanf()【通过键盘将数据输入到变量中】

image-20220607203648850

​ 两种用法:

​ 用法一:scanf(“输入控制符”,输入参数);

​ 功能:将键盘输入的字符转换为输入控制符所规定格式的数据,然后存入以输入参数的值为地址的变量中

#include <stdio.h>

int main(void)
{
	int i;
	
	scanf("%d",&i);	//&i 表示i的地址 &是一个取地址符
	//%d的作用:把键盘输入的这些合法的字符转换为一个10进制数字 
	printf("i = %d\n",i);
	 
	return 0;
} 

​ 用法二:scanf(“非输入控制符 输入控制符”,输入参数);

​ 功能:将键盘输入的字符转换为输入控制符所规定格式的数据,然后存入以输入参数的值为地址的变量中,非输入控制符必须原样输入

#include <stdio.h>

int main(void)
{
	int i;
	
	scanf("m%d",&i);	//m123 正确的输入,123是非法的输入 
	//此处就算输入其它字母也都会被认为是非法字符,因为这里的输入控制符是%d而不是%c 
	printf("%d\n",i);


	return 0;
} 

​ 如何使用scanf编写出高质量代码

​ 1.使用scanf之前最好先使用printf提示用户以什么样的方式来输入

​ 2.scanf中尽量不要使用非输入控制符,尤其不要使用\n

​ 3.应该编写代码对用户的非法输入做适当的处理

	while ((ch = getchar()) != '\n')
		continue;
#include <stdio.h>

int main(void)
{
	int i,j;
	char ch;
	
	printf("请输入两个数,以空格隔开\n");
	scanf("%d %d",&i,&j);
	printf("i = %d,j = %d\n",i,j);
	
	//过滤垃圾字符
	while((ch = getchar()) != '\n')
		continue; 
	
	int a;
	
	scanf("%d",&a);
	printf("a = %d\n",a);
	
	return 0;
}

运算符

image-20220607204020895

image-20220608125055036

#include <stdio.h>

int main(void)
{
	printf("%d %d %d %d %d %d \n",3%3,13%-3,-13%3,-13%-3,-13%23,3%5);
	 
	return 0;
} 

/*
	输出结果是:
	-------------------------
	0 1 -1 -1 -13 3
	------------------------- 
	
	总结:取余%的运算对象必须是整数,结果是整除后的余数,其余数的符号和被除数相同
*/

image-20220607204045076

#include <stdio.h>

int main()
{
	int i = 10;
	int k = 20;
	int m,n,b;
	
//	m = (3 > 2) && (k = 8);
//	printf("m = %d,k = %d\n",m,k);//m = 1,k = 8

	b = (3 > 2) && (k = 0);
	printf("b = %d,k = %d\n",b,k);//b = 0,k = 0
	
	n = (3 < 2) && (k = 8);
	printf("n = %d,k = %d\n",n,k);//n = 0,k = 20
	
	
	return 0;
} 

流程控制【c语言的第一个重点】

什么是流程控制

​ 程序代码执行的顺序

流程控制的分类

顺序

自上而下依次执行

选择

​ 定义

​ 某些代码可能执行,也可能不执行,有选择的执行某些代码

​ 分类

if

1.if最简单的用法

格式:
	if(表达式)
		语句
功能:
	如果表达式为真,执行语句
	如果表达式为假,不执行语句

2.if的范围问题

if(表达式)
	语句A;
	语句B;
解释:if默认只能控制语句A的执行或不执行,无法控制语句B的执行或不执行,或者说语句B一定会执行
if(表达式)
{
	语句A;
	语句B;
}
此时if可以控制语句A和语句B
    由此可见:if只能控制一个语句的执行或不执行,如果想控制多个语句,就必须把这些语句用{}阔起来
if…else…

4.if…else if…else…的用法

if (1) 
	A: 
else if (2) 
	B: 
else if (3) 
	C: 
else 
	D;

5.C语言对真假的处理

非零是真
零就是假
真用1表示
假用零表示

6.if举例–求分数的等级

#include<stdio.h>

int main(void)
{
	float score;
	
	printf("请输入你的成绩:\n");
	scanf("%f",&score);
	
	if(score > 100)
		printf("在做梦吧?\n");
	else if(score >= 90 && score <= 100)
		printf("优秀\n");
	else if(score >= 80 && score <= 90)
		printf("良好\n");
	else if(score >= 60 && score <= 80)
		printf("及格\n");
	else if(score >= 0 && score <= 60)
		printf("不及格\n");
		
	return 0;
}

两数交换

#include <stdio.h>

int main(void)
{
	int i,j;
	char ch;
	
	printf("请输入i的值:");
	scanf("%d",&i);
	printf("\n");
	
	while((ch = getchar()) != '\n')
		continue;
	
	printf("请输入j的值:");
	scanf("%d",&j);
	printf("\n");
	
	int temp;
	
	temp = i;
	i = j;
	j = temp;
	
	printf("两变量交换后的值为:i = %d,j = %d\n",i,j);
	
	return 0;
}

三数排序

思路
如果a > b 
	则a与c做比较,a > c则输出a,a < c则输出c
否则(a<b)
	b与c作比较,b > c则输出b,b < c则输出c
修改思路
如果 a < b
	则a与b交换位置
如果 a < c
	则a与c交换位置
如果 b < c
	则b与c交换位置
输出a,b,c
int main(void)
{
	int a,b,c;
	int temp;
	
	printf("请随意输入三个数,以空格隔开");
	printf("\n");
	scanf("%d %d %d",&a,&b,&c); 
	
	if(a < b)
	{
		temp = a;
		a = b;
		b = temp;
	}
	
		if(a < c)
	{
		temp = a;
		a = c;
		c = temp;
	}
		if(b < c)
	{
		temp = b;
		b = c;
		c = temp;
	}
	printf("%d %d %d\n",a,b,c);
	
	return 0;
}
补充:
小算法的程序:
判断一个数字是否是素数
判断一个数字是否是回文数
编程实现求一个十进制数字的二进制形式
求一个数字的每位是奇数的数字取出来组合形成的新数字
求一个数字倒过来的数字

如何一些小算法的程序
尝试自己去编程解决它,大部分人都自己无法解如果解决不了,就看答案
关键是把答案看懂,这个要花很大的精力,也是我们学习的重点
看懂之后尝试自己去修改程序,并且知道修改之后程序的不同输出结果的含义
照着程序去敲
调试错误
不看答案,自己独立把答案敲出来
如果程序实在无法彻底理解, 就把它背会

7.if常见问题解析

1.空语句的问题
	if (3 > 2);
	等价于
	if (3 > 2)
		;//这是一个空语句
		
2.
	if (表达式1)
		A;
	else 
		B;
	是正确的
	
	
	if (表达式1);
		A;
	else 
		B;
	是错误的

3.
	if(表达式1)
		A;
	else if(表达式2)
		B;
	else if(表达式3)
		C;
	else
		D;
	即使表达式1和2都成立,也只执行A语句
	
4.
	if(表达式1)
		A;
	else if(表达式2)
		B;
	else if(表达式3)
		C;
	这样写不会报错,但是逻辑上有问题,相当于缺少最后一个约束条件(else的条件)
	
5.
	if(表达式1)
		A;
	else if(表达式2)
		B;
	else if(表达式3)
		C;
	else(表达式4)
		D;
	这样的写法是不对的(报语法错误),要不然去掉48行的表达式4,要不然在48行的else后加if
	
6.
	if(表达式1)
		A;
	else if(表达式2)
		B;
	else if(表达式3)
		C;
	else(表达式4);
		D;
	这样写语法不会出错,但逻辑上是错误的
	相当于
		else
			(表达式);
		D;
switch
# include <stdio.h>

int main(void)
{
	int val;
	
	printf("请输入您要进入的楼层:");
	scanf("%d",&val);
	
	switch(val)
	{
	case(1):
		printf("1层开!\n");
		break;
	case(2):
		printf("2层开!\n");
		break;
	case(3):			//程序的入口 
		printf("3层开!\n");
		break;			//程序中的break表示着跳出switch,如果没有break,将一直运行到程序结束 
	default:
		printf("还没盖呢!\n");
		break;			//去掉最后一行break不会再次循环回去,程序中switch运行完成 
	}
	
	
	return 0;
}

image-20220628222954540

image-20220628223157761

image-20220628223222474

循环

什么是循环:

​ 某些代码会被重复执行

循环的分类

for
  1. 格式:
for(1;2;3)
	A;
  1. 执行的流程【重点】

    单个for循环的使用

举例:

# include <stdio.h>

int main(void)
{
	int i;
	int sum = 0;
	
	for(i = 1;i <= 4;i++)
		sum = sum + i;
		
	printf("sum = %d\n",sum);
	
	return 0;
}
/*
运行结果
-----------------------------
sum = 10
-----------------------------
分析:for循环
进入循环,先执行i = 1,再执行i <= 4,当满足语句2时,执行sum = sum + i;,循环体执行结束后执行i++,i自加(否则会成为一个死循环),至此,循环执行一次。之后再次执行语句2,来判断是否满足条件,满足继续执行循环体,不满足则跳出循环。
1243	1
243		3
243		6
243		10
2	跳出,执行	printf("sum = %d\n",sum);
*/
#include<stdio.h>

int main(void)
{
    int i;
    int sum = 0;
    
    for(i = 0;i < 5;i++)
    {
        sum = sum + i;		//执行了5次
	}
    
    printf("sum = %d\n";sum);
    return 0;
}
#include<stdio.h>

int main(void)
{
    int i;
    int sum = 0;
    
    for(i = 0;i < 1000;i++)
    {
        sum = sum + i;		//由上程序可知,执行了1000次,
	}
    
    printf("sum = %d\n";sum);
    return 0;
}
#include<stdio.h>

int main(void)
{
    int i;
    int sum = 0;
    
    for(i = 56;i < 149;i++)
    {
        sum = sum + i;		//由上程序可知,执行了93次,
	}
    
    printf("sum = %d\n";sum);
    return 0;
}

for和if的嵌套使用

# include <stdio.h>

/*
	2022年6月10日21:09:24
	求1-10中能被3整除的数之和 
*/
int main(void)
{
	int i;
	int sum = 0;
	
	for(i = 1;i <= 10; ++i)
	{
		if(i % 3 == 0)
		{
			sum = sum + i;
		}
		printf("sum = %d\n",sum);
	}
//	printf("sum = %d\n",sum);
	
	return 0;
}
/*
	输出结果为: 
	sum = 0
	sum = 0
	sum = 3
	sum = 3
	sum = 3
	sum = 9
	sum = 9
	sum = 9
	sum = 18
	sum = 18
i=	1	2	3	4	5	6	7	8	9	10
sum=0	0	3	3	3	9	9	9	18	18
*/ 

多个for循环的嵌套使用

1、

for (1; 2; 3) //1 
	for (4; 5; 6) //2 
		A; //3 
		B; //4 
整体是两个语句 1 2 3 是一个语句
			 4是一个语句

2、

for (1 ; 2; 3) 
	for (4; 5; 6)
    {
        A: 
		B; 
    }
整体是一个语句

3、

for (7 ; 8; 9) 
	for (1; 2; 3) 
	{
		A: 
		B; 
		for (4; 5; 6) 
			C; 
	}
整体是一语句
  1. 范围问题

  2. 举例:

1 + 2 + 3 +… …+ 100

1 + 1/2 + 1/3 +. … + 1/100

【本程序对初学者而言很重要,具体细节可参见我录制的相关视频】

注意:
  1. for循环中的变量‘i’不能定义为浮点型(float),因为浮点型是非准确存储大一点点或小一点点存储的。如 i <=100 有可能最后加到100时比100大一点点而不能参加循环。
  2. for的多重循环,循环次数较少的放在外面;循环次数较多的放在里面。用来减少CPU跨切循环层的次数。
  3. for循环的语法定义 ++i和 i++的结果是一样的 .但是i++由于是在使用当前值之后再+1,所以需要一个临时的变量来转存i。而++i则是在直接+1,省去了对内存的操作的环节,相对而言能够提高性能。
while
  1. 执行顺序

格式:

while (表达式)
	语句;
  1. 与 for 的相互比较
    for和while可以相互转换

    for(1;2;3)
    	A;
    	
    1;
    while(2)
    {
    	A;
    	3;
    }
    
  2. 举例
    从键盘输入一个数字,如何该数字是回文数,则返回yes,否则返回no,
    回文数:正着写反着写都一样。例如12321 212

    # include <stdio.h>
    
    int main(void)
    {
    	int val;//存放待判断的数字
    	int m;
    	int sum = 0;
    	
    	printf("请输入待判断的值:\n");
    	scanf("%d",&val);
    	
    	m = val;
    	while(m)
    	{
    		sum = sum*10 + m%10;
    		m /= 10;
    	}
    	
    	if(sum == val)
    		printf("yes\n");
    	else
    		printf("no\n");
    		
    	return 0; 
    }
    
    #include<stdio.h>
    //菲波拉契序列
    int main(void)
    {
    	int f1,f2,f3;
    	int n;
    	int i;
    	
    	printf("请输入您需要求的序列: ");
    	scanf("%d",&n);
    	
    	f1 = 1;
    	f2 = 2;
    	
    	if(n == 1)
    		f3 = 1;
    	else if(n == 2)
    		f3 = 2;
    	else
    		for(i = 3;i <= n;++i)
    		{
    			f3 = f2 + f1;
    			f1 = f2;
    			f2 = f3;
    		}
    	
    	printf("%d\n",f3);
    	
    	return 0;
    }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Li6tsP4T-1657855358772)(C:\Users\张智超\Desktop\线性代数\image-20220628112113860.png)]

  3. 什么时候使用while,什么时候用for
    没法说,用多了自然就知道了

do…while

​ 格式

do
{
.......
}while(表达式)
    //先执行一次才去判断条件

do…while.并不等价于for,当然也不等价于while

# include <stdio.h>
# include <math.h>

int main(void) 
{
	double a,b,c;
	double x1,x2;
	double delta;
	char ch;

	do 
	{
		printf("请输入a的值:");
		scanf("%lf",&a);

		printf("请输入b的值:");
		scanf("%lf",&b);

		printf("请输入c的值:");
		scanf("%lf",&c);

		delta = b*b - 4*a*c;

		if(delta > 0) 
		{
			x1 = (-b + sqrt(delta)) / (2*a);
			x2 = (-b - sqrt(delta)) / (2*a);
			printf("此方程有两个不同的解,x1 = %f,x2 = %f\n",x1,x2);
		} 
		else if(delta == 0) 
		{
			x1 = x2 = (-b) / (2*a);
			printf("有唯一解,x1 = x2 = %lf\n", x1);
		} 
		else 
		{
			printf("无实数解!\n");
		}
		
		printf("继续计算请输入Y,或输入任意字符退出");
		scanf(" %c", &ch);  //%c前面必须得加一个空格 原因略
	}while(ch == 'y' || ch == 'Y');


	return 0;
}
break和continue
break
  1. 用于循环是用来终止循环

  2. 用于swtich是用来终止Switch

  3. 不能直接用于if,除非if属于循环内部的一个字句

    for(i = 3;i <= 5;++i)
    {
        if(3>2)
            break;//break虽然是if内部的语句,但是break终止的是外部的for循环
        printf("嘿嘿!\n");//永远不会输出
    }
    
  4. 在多层循环中,break只能终止距离它最近的循环

    for(i = 0;i<3;++i)
    {
        for(j = 1;j<5;++j)
            break;//break只能终止距离它最近的循环
        printf("同志们好\n");
    }
    /*
    输出结果为:
    ---------------------
    同志们好
    同志们好
    同志们好
    ---------------------
    */
    

    在多层switch中,break也只能终止距离它最近的switch

    # include <stdio.h>
    
    int main()
    { 
    	int x=1, y=0, a=0, b=0;
    	switch(x) // 第一个switch 
    	{  
    	case 1:  
    		switch(y)  // 第二个switch 
    		{  
    		case 0:  
    			a++; 
    			break; //终止的是第二个switch  
    		case 1:  
    			b++; 
    			break;
    		}
    		b = 100;
    		break;   //终止的是第一个switch
    	case 2: 
    		a++;    
    		b++;
    		break;  
    	}	
    	printf("%d %d\n",a,b); //26行
    	
    	return 0;
    }
    输出结果为:1 100
    
continue

用于跳过本次循环余下的语句,转去判断是否需要执行下次循环

# include <stdio.h>

int main(void)
{
	int i;
	char ch;

	scanf("%d", &i);
	printf("i = %d\n", i);


	while ( (ch=getchar()) != '\n')
		continue;
	int j;
	scanf("%d", &j);
	printf("j = %d\n", j);

	return 0;
}

穿插在课堂中的零散知识笔记

算法

算法就是:解题的方法和步骤

如何看懂一个程序,分三步:
  1. 流程

  2. 每个语句的功能

  3. 试数

如何学习一些需要算法的程序【如何掌握一个程序】
  1. 尝试自己去编程解决它
    但要意识到大部分人都是自己无法解决的,这时不要气馁,也不要自卑,如果十五分钟还想不出来, 此时我建议您就可以看答案了。
  2. 如果解决不了, 就看答案
    关键是把答案看懂, 这个要花很大的精力,也是我们学习的重点,看懂一个程序要分 3步:流程、每个语句的功能、 试数
  3. 看懂之后尝试自己去修改程序,并且知道修改之后程序的输出结果的含义
    不建议看懂程序之后就立即自己敲程序
  4. 照着答案去敲
  5. 调试错误
  6. 不看答案,自己独立把答案敲出来
  7. 如果程序实在无法彻底理解, 就把它背会,不过无法彻底理解的程序非常少,

我自己只有在学数据结构时碰到过一个, 学其他语言都没有碰到过

强制类型转化

格式:

​ (数据类型) (表达式)

功能:

​ 把表达式的值强制转化为前面所执行的数据类型

例子:

​ (int) (4. 5+2. 2) 最终值是6

​ (f1oat) (5) 最终值是 5.000000

浮点数的存错所带来的问题

float double都不能保证可以把所有的实数都准确的保存在计算机中

例子

​ float i = 99.9:

​ printf(“%f\n”,i);

​ 最终在Vc++6.0 中的输出结果是: 99. 900002

因为浮点数无法准确存储,所以就衍生出来两个编程问题

有一个浮点型变量X ,如何判断X的值是否是零

	if (|X-0. 000001| <= 0.000001) 
​		是零
​	else 
​		不是零

为什么循环中更新的变量不能定义成浮点型

	因为浮点数据在计算机中不是精确的数据,因此,有可能不连续,或是运算出BUG
	因此,不建议用浮点数做循环变量!但是可以用。
一些琐碎的运算符知识
自增[或者自减]
  1. 分类:
    前自增 ++i
    后自增 i++
  2. 前自增和后自增的异同:
    相同:最终都使i的值加1
    不同:前自增整体表达式的值是i加1之后的值
    后自增整体表达式的值是i加1之前的值
  3. 为什么会出现自增
    代码更精练
    自增的速度更快
  4. 学习自增要明自的几个问题:
    1、我们编程时应该尽量屏蔽掉前自增和后自增的差别
    2、自增表达式最好不要作为一个更大的表达式的一部分来使用
    或者说
    i++ ++i 单独成一个语句,不要把它作为一个完整复合语句的一部分来使用
    如:
int m = i++ + ++i + i + i++; //这样写不但是不规范的代码
							//而且是不可移植的代码
printf("%d %d %d",i++,++i,i); //同
三目运算符

格式

​ A ? B : C

等价

if(A)
B;
else
C;
逗号表达式

格式(A, B, C, D)

功能:

从左到右执行

最终表达式的值是最后一项的值

# include <stdio.h>

int main(void)
{
	int i = (1,2,3,4);
	
	int j = (i++,++i,i+2,i-2);
	
	printf("i = %d,j = %d\n",i,j);
	
	return 0;
} 

/*
输出结果为
--------------------
i = 6,j = 4
-------------------- 
*/ 

数组

为什么需要数组

为了解决大量同类型数据的存储和使用问题

为了模拟现实世界

数组的分类

一维数组
怎样定义一维数组

为n个变量连续分配存储空间

# include <stdio.h>

int main(void)
{
	int a[5] = {5,8,6,31,4};
	int i;
	
	for(i = 0;i < 5;i++)
	{
		printf("%p\n",&a[i]);//注意!输出的是看逗号后面写什么,前面的只是控制输出格式的符号,因为要输出的是变量的地址,所以要加上取地址符 
	}
	
	return 0;
}
/*
输出结果为:
-----------------
000000000062FE00
000000000062FE04
000000000062FE08
000000000062FE0C
000000000062FE10
----------------- 

*/ 

所有的变量数据类型必须相同

所有变量所占的字节大小必须相等

​ 例子:

 int a[5]
 	一维数组名不代表数组中所有的元素
 	一维数组名代表数组第一个元素的地址
当我们定义一个数组a 时,编译器根据指定的元素个数和元素的类型分配确定大小(元素类型大小*元素个数)的一块内存,并把这块内存的名字命名为a。名字a 一旦与这块内存匹配就不能被改变。a[0] a[1]等为a 的元素,但并非元素的名字。数组的每一个元素都是没有名字的。
有关一维数组的操作
初始化
完全初始化
	int a[5] = {1,2,3,4,5};
	
不完全初始化,未被初始化的元素自动为零
	int a[5] = {1,2,3};
	
不初始化,所有元素都是垃圾值
	int a[5]
	
清零
	int a[5] = {0};

错误写法:

	int a[5];
	a[5] = {1, 2, 3, 4, 5} ; / /错误
只有在定义数组的同时才可以整体赋值,
其他情况下整体赋值都是错误的

	int a[5] = {1, 2, 3, 4, 5} ; 
	a[5] = 100 ; //error 困为没有a[5]这个元素,最大只有a[4]
	
	int a[5] = {1, 2, 3, 4, 5} ; 
	int b[5] ; 
如果要把a中的值全部复制给b数组
错误的写法
	b = a;/ / error 
正确的
	for (i=0 ; i < 5 ; ++i) 
	b [i] = a [i];
赋值
排序
#include<stdio.h>

int main(void)
{
	int i,j;
	int temp;
	int date[10] = {45,89,1,464,41,41,2120,14552,78,62};
	
	for(i = 0;i<10-1;i++)
	{
		for(j = 0;j < 10-i-1;j++)
		{
			if(date[j] < date[j+1])	//降序 
			{
				temp = date[j+1];
				date[j+1] = date[j];
				date[j] = temp;
			}
		}
	}
	
	for(i = 0;i < 10;i++)
	{
		printf("经过排序后的数组元素为:%d\n",date[i]);
	}
	
	return 0;
}
# include <stdio.h>

//冒泡排序
void sort(int * a, int len)
{
	int i, j, t;

	for (i=0; i<len-1; ++i)
	{
		for (j=0; j<len-1-i; ++j)
		{
			if (a[j] > a[j+1])  // >表示升序 <表示降序
			{
				t = a[j];
				a[j] = a[j+1];
				a[j+1] = t; 
			}
		}
	}
}

int main(void)
{
	int a[6] = {10, 2, 8, -8, 11, 0};
	int i = 0;

	sort(a, 6);

	for (i=0; i<6; ++i)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	return 0;
}
求最大/小值
倒置
#include<stdio.h>

int main(void)
{
	int a[8] = {1,2,3,4,5,6,7,8};
	
	int i,j;
	int t;
	
	i = 0;
	j = 7;
	
	while(i < j)
	{
		t = a[i];
		a[i] = a[j];
		a[j] = t;
		
		//设置i和j的规则,避免程序死循环
		i++;
		j--; 
	}
	
	for(i = 0;i < 8;i++)
	{
		printf("a[%d] = %d\n",i,a[i]);
	}
	
	return 0;
}
查找
插入
删除
二维数组

格式

int a[3][4];

定义

int a[3][4];
总共是12个元素,可以当做3行四列看待,这12个元素的名字依次是
a [0][0] a[0][1] a[0][2] a[0][3]
a [1][0] a[1][1] a[1][2] a[1][3] 
a [2][0] a[2][1] a[2][2] a[2][3] 
a[i][j] 表示第i+1行第j+1列的元素
int a[m][n];该二维数组右下角位置的元素只能是a[m-1][n-1]

初始化

int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
int a[3][4] = {
		{1, 2, 3, 4}
		{5, 6, 7, 8}
		{9,10,11,12}
};

操作

输出二维数组内容:
int a[3][4] = {
		{1, 2, 3, 4}
		{5, 6, 7, 8}
		{9,10,11,12}
};
int i,j;

//输出数组内容
for(i = 0;i < 3;i++)
{
	for(j = 0;j < 4;j++)
		printf("%d",a[i][j]);
	printf("\n");
}

/*
 二维数组3行4列输出 
int a[3][4] = { {1, 2, 3, 4}, 
{1, 2, 3, 4}, 
{1, 2, 3, 4}}; 
int i, j; 
 for(i=0; i<3; i++) 
 { 
for(j=0; j<4; j++) 
{ 
printf("a[%d][%d] = %d  ", i, j, a[i][j]);//每四个一行 
} 
printf("\n");//换行 
 }
*/

对二维数组排序

求每一行的最大值

判断矩阵是否对称

矩阵的相乘

多维数组

是否存在多维数组?

​ 不存在

​ 因为内存是线性一维的

​ n维数组可以当做每个元素是n-1 维数组的一维数组

​ 比如:

		int a[3][4]; 

​ 该数组是含有3个元素的一维数组

​ 只不过每个元素都可以再分成4个小元素

int a [3][4][5]; 

​ 该数组是含有3个元素的一维数组

​ 只不过每个元素都是4行5列的二维数组

函数【c语言的第二个重点】

为什么需要函数

  1. 避免了重复性操作
  2. 有利于程序的模块化

什么叫函数

​ 逻辑上:能够完成特定功能的独立的代码块

# include<stdio.h>

//max是函数的名字,i和j是形式参数,简称形参,void表示函数没有返回值
void max(int i,int j)
{
	if(i > j)
	{
		printf("%d\n",i);
	}
	else
	printf("%d\n",j);
}

int main(void)
{
	int a,b,c,d,e,f;
	a = 1,b = 2,c = 3,d = 9, e = -5;f = 100;
	
	max(a,b);
	max(c,d);
	max(e,f);
	
	return 0;
}

​ 物理上:

​ 能够接受数据【当然也可以不接受数据】

​ 能够对接受的数据进行处理

​ 能够将数据处理的结果返回【当然也可以不返回任何值】

​ 总结:函数是个工具,它是为了解决大量类似问题而设计的

​ 函数也可以当做一个黑匣子

#include<stdio.h>

int f(void) //括号中的void表示该函数不能接受数据  int表示函数返回值是int类型的数据
{
	return 79;//向主调函数返回10
}

void g(void)  //8行  函数名前面的void表示该函数没有返回值
{
//	return 10; //error 与8行行首的void相矛盾
}

int main(void)
{
	int i = 50;
	
	i = f();
	
	//	j = g(); //error 因为g函数没有返回值
	
	printf("%d\n",i);
	
	return 0;
}

如何定义一个函数

函数的返回值类型 函数的名字(函数的形参列表)
{
	函数的执行体
}
  1. 函数定义的本质是:详细描述函数之所以能实现某个特定功能的具体实现方式

  2. return表达式:return用来结束整个函数。无论出现多少个return,执行第一个return后整个函数就结束了,不会再执行其他的return,即也不会再执行其他的语句了。
    1> 终止被调函数,向主调函数返回表达式的值
    2> 如果表达式为空,则只终止函数,不向主调函数返回任何值
    3> break是用来终止循环和switch的,return使用来终止函数的

    void f()
    {
        return; //return 只用来终止函数,不向主调函数返回任何值
    }
    
    int f()
    {
        return 10;//第一:终止函数,第二:向主调函数返回10
    }
    
  3. 函数返回值的类型也称为函数的类型,因为如果 函数名前的返回值类型 和 函数执行体中的 return 表达式; 中表达式类型不同的话,则最终函数返回值的类型 以函数名前的返回值类型为准。

    int f()
    {
    	return 10.5;//因为函数的返回值类型是int
        			//所以最终f返回的是10,不是10.5
    }
    

    在c语言中,凡不加返回值类型限定的函数,就会被编译器作为 返回整形值 处理。但许多程序员却误以为其为void类型。

    add ( int a, int b ) 
    { 
    	return a + b; 
    } 
    int main(int argc, char* argv[]) 
    { 
    	printf ( "2 + 3 = %d", add ( 2, 3) ); 
    } 
    /*
    程序运行的结果为输出: 
    ------------------
    2 + 3 = 5 
    ------------------
    */
    
    
    # include <stdio.h>
    
    add(float x,float y)
    {
    	return x + y;
    }
    
    int main(void)
    {
    	float a,b;
    	
    	printf("请输入两浮点数a,b的值:\n");
    	scanf("%f %f",&a,&b);
    	
    	printf("两数之和为%f\n",add(a,b));
    	
    	return 0;
    }
    
    /*
    程序运行的结果为输出: 
    ------------------
    两数之和为0.000000
    ------------------
    */
    这说明不加返回值说明的函数的确为int函数
    
# include <stdio.h>

int f(int i)
{
	return 10.8;
}

int main(void)
{
	float i = 99.9;
	printf("%f\n", i);
	
	i = f(5);
	printf("%f\n", i);

	return 0;
}

函数的分类

有参函数 和 无参函数

有返回值函数 和 无返回值函数

值传递函数 和 地址传递函数

普通函数 和 主函数(main函数)

重点

一个程序必须有且只能有一个主函数

主函数可以调用普通函数 普通函数不能调用主函数

普通函数可以相互调用

主函数是程序的入口,也是程序的出口

注意的问题

函数调用和函数定义的顺序

​ 如果函数调用卸载了函数定义的前面,则必须加函数前置声明

函数前置声明:

  1. 告诉编译器即将出现的若干个字母代表的是一个函数
  2. 告诉编译器即将出现的若干个字母所代表的函数的形参和返回值的具体情况
  3. 函数声明是一个语句,末尾必须加分号
  4. 对库函数的声明是通过#include<库函数所在的文件的名字.h>来实现的
# include <stdio.h>

void f(void); //函数声明, 分号不能丢掉
//无函数声明时,主程序里遇到f();不会认为它是函数
//并且,函数声明必须与函数的返回值类型和形参列表相同,否则无法找到函数,程序将报错

int main(void)
{
	
	f();
	
	return 0;
}

//如果自定义函数放到main函数后,需要在主函数前加上本函数的函数声明
//否则当主函数调用自定义函数时,将会找不到函数体,因为程序是自下而上依次运行的
//而main函数做为程序的入口先执行 
void f(void)
{
	printf("测试函数声明\n");
}
# include <stdio.h>

void f(int); //函数声明, 分号不能丢掉
//程序找不到 void f(int) 函数,函数声明不许与自定义函数一致 
int main(void)
{
	f();

	return 0;
}

void f(void)
{
	printf("哈哈!\n");
}
//此程序是错误的 
# include <stdio.h>

//void f(void);

void g(void)
{
	f();//因为f函数放到了g函数之后,程序无法找到f函数 
}

void f(void)
{
	printf("ll\n");
}

int main(void)
{
	
	g();
	
	return 0;
}

形参和实参

​ 个数相同 位置一一对应 数据类型必须相互兼容

函数的形参和实参定义的变量名可以相同。因为在一个函数内部定义的变量只能在本函数中使用,实参的值传递给形参而不是变量名。本函数结束后函数内部定义的变量将被释放。
形参只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元,因此,形参只在本函数内部有效。
# include <stdio.h>

void f(int i, float x)
{
	printf("%d\n", i);
}

int main(void)
{
	f(9.9, 6.6);

	return 0; 
}

/*
程序运行结果为
-------------------------------
9
------------------------------- 
*/ 

如何在软件开发中合理的设计函数来解决问题

​ 一个函数的功能尽量独立,单一

​ 多学习,多模仿牛人的代码

函数是c语言的基本单位,类是Java,c#,c++的基本单位

//程序1,判断素数
/*
	2022年7月8日17:21:57
	判断一个数字是否是素数
	只用一个函数实现,不好,代码的利用率不高	
*/

# include <stdio.h>

int main(void)
{
	int i;
	int val;
	
	scanf("%d",&val);
	
	for(i = 2;i < val;i++)
	{
		if(val % i == 0)
			break;
	}
	if(i == val)
		printf("Yes!");
	else
		printf("No!");
	
	return 0;
}
/*
	2022年7月8日17:27:52 
	用自定义函数实现判断一个数字是否是素数
	用单独的函数来实现, 代码的可重用性提高	
*/

# include <stdio.h>

bool IsPrime(int m)
{
	int i;
	for(i = 2;i < m;i++)
	{
		if(m % i == 0)
			break;
	}
	if(i == m)
		return true;
	else
		return false;
	
}

int main(void)
{
	int val;
	
	scanf("%d",&val);
	
	if(IsPrime(val))
		printf("Yes!\n");
	else
		printf("No!\n");


	return 0;
}
/*
	2009年11月4日11:18:51
	求1到某个数字之间(包括该数字)所有的素数,并将其输出
	只用main函数实现,有局限性:
		1. 代码的重用性不高
		2. 代码不容易理解		
*/

# include <stdio.h>

int main(void)
{
	int val;
	int i;
	int j;

	scanf("%d", &val);
	for (i=2; i<=val; ++i)
	{
		//判断i是否是素数,是输出,不是不输出
		for (j=1; j<i; ++j)
		{
			if (0 == i%j)
				break;
		}
		if (j == i)
			printf("%d\n", i);
	}

	return 0;
}
/*
	2009年11月4日11:18:51
	求1到某个数字之间(包括该数字)所有的素数,并将其输出
	用1个函数来判断一个数字是否是素数
		优点:
			代码比  如何设计函数_3.cpp  更容易理解
			代码的可重用性比  如何设计函数_3.cpp 高
		缺点:
			可重用性仍然不是非常高,
			比如有1000个数字,求它们每个数字从1到它本身的素数
			则
					for (i=2; i<=val; ++i)
					{
						if ( IsPrime(i) )
							printf("%d\n", i);
					}
			要写1000次
	
*/

# include <stdio.h>

bool IsPrime(int m)
{
	int i;
		
	for (i=2; i<m; ++i)
	{
		if (0 == m%i)
			break;
	}
	if (i == m)
		return true;
	else
		return false;

}

int main(void)
{
	int val;
	int i;
 
	scanf("%d", &val);
	for (i=1; i<=val; ++i)
	{
		if ( IsPrime(i) )
			printf("%d\n", i);
	}

	return 0;
}
/*
	2009年11月4日11:56:29
	用两个函数来实现求1到某个数字之间所有的素数,并将其输出
	本程序 和 如何合理设计函数_4.cpp 相比较
	代码量更少,可重用性更高
*/


# include <stdio.h>

//本函数的功能是: 判断m是否是素数,是返回true,不是返回false
bool IsPrime(int m)
{
	int i;
		
	for (i=2; i<m; ++i)
	{
		if (0 == m%i)
			break;
	}
	if (i == m)
		return true;
	else
		return false;

}

//本函数的功能是把1到n之间所有的素数在显示器上输出
void TraverseVal(int n)
{
	int i;

	for (i=1; i<=n; ++i)
	{
		if ( IsPrime(i) )
			printf("%d\n", i);
	}
}

int main(void)
{
	int val;
 
	scanf("%d", &val);
	TraverseVal(val);

	return 0;
}

常用的系统函数

double sqrt(double x); 
求x的平方根

int abs (int x) 
求x的绝对值

double fabs(double x) 
求x的绝对值

专题

​ 递归

​ 可以参见数据结构视频

变量的作用于和存储方式

按作用域分:
全局变量(又称“外部变量”):

在所有函数外部定义的变量叫全局变量

全局变量使用范围:从定义位置开始到整个函数结束

局部变量

在一个函数内部定义的变量 或者 函数的形参 都统称为局部变量

void f(int i)
{
	int j = 20
}
//i和j都属于局部变量

局部变量使用范围:只能在本函数内部使用

注意的问题:
全局变量和局部变量命名冲突的问题

在一个函数内部如果定义的局部变量的名字和全局变量名一样时,局部变量会屏蔽掉全局变量。

就近原则,这点与java中类似

# include <stdio.h>

int k = 1000;

int f(void)
{
	int k = 5;
	printf("%d\n",k);
}

int main(void)
{
	f();
	
	printf("%d\n",k);
	
	return 0;
}
按变量的存储方式

静态变量

自动变量

寄存器变量

指针(C语言的第三个重点)

一个字节为8位。数据存储是以“字节(Byte)”为单位,数据传输是以大多是以“位”(bit又名比特)为单位,一个位就代表一个0或1(即二进制),每8位组成一个字节,是最小一级的信息单位。

学习指针主要是为了学习动态内存分配和跨函数使用内存

指针热身

指针的重要性

表示一些复杂的数据结构

快速的纯涤数据,减少了内存的好用【重点】

使函数返回一个以上的值【重点】

直接访问硬件

能够方便的处理字符串

是理解面向对象语言中引用的基础

总结:指针是c语言的灵魂

指针热身
# include <stdio.h>

int main(void)
{
	int * p;
	int i = 3;
	
	p = &i;
	
	printf("%p\n",*p);
	printf("%p\n",i);
	/*
		0000000000000003
		0000000000000003
	*/
	
	//p = i; //error,因为类型不一致,p只能存放int类型变量的地址,不能存放int类型变量的值
	//p = 55; //error 原因同上
	return 0;
}

*的意思是读一个地址指向的内容,即读取该指针变量 p 中存放的地址所对应的内容

# include <stdio.h>
 
int main(void)
{
	int * p;//p是变量的名字,int * 表示 p 变量存放的是int类型变量的地址
			//int * p;表示的不是定义了一个名字叫做*p的整形变量
			//int * p;应该这样理解:p是变量名,int * 是 p 变量的数据类型
			//所谓 int * 类型 实际上就是存放 int 变量地址的类型 
			//p就是指针变量,也可以叫地址变量 
	int i = 3;
	int j;
	
	p = &i;
	/*
		1.p保存了i的地址,因此p指向了i
		2.p不是i,i也不是p,更准确的说:修改i的值,并不会影响p的值,同理,修改p的值也并不会影响i
		3.在声明int * p 中 * 表示这个变量是int类型的指针(地址),
		  在声明之外*可以理解成一个运算符  * 和 & 是一对逆运算 
		  & 的意思是读一个变量的地址,比如你声明了int a;&a是a的地址可能是xx1001之类的
		  * 的意思是读一个地址指向的内容,即读取该指针变量中存放的地址的内容 
		4.如果一个指针变量指向了一个普通变量,则 *指针变量 就完全等同于 这个普通变量 
		  例子:
		  		如果 p 是个指针变量,并且 p 存放了普通变量 i 的地址
				则 p 指向了普通变量 i
				*p 就完全等同于i
				或者说:在所有出现*p的地方都可以替换成i
						在所有出现i的地方都可以替换成*p
						
				*p 就是以指针变量 p 存放的地址所对应的内容为地址的变量
			
	*/ 
	j = *p; //等价于 j = i;
	printf("i = %d, j = %d\n", i, j); 
}

image-20220709105031600

自己对指针的理解

# include <stdio.h>

int main(void)
{
	int i = 3;
	int * p;
	int j;
	
	p = &i;
	
	printf("%d\n",p);//6487572
	printf("%d\n",&i);//6487572
	//说明p中存储的是i的地址
	
	p =  &j;
	printf("%d\n",&i);//6487572
	//改变p的值,并不影响i
	
	i = 5;
	p = &i;
	printf("%d\n",p);//6487572 
	//改变i的值并不影响p,因为p里面存放的是i的地址
	
	*p = i;
	printf("%d\n",*p);//5 
	
	return 0;
}
指针和指针变量

指针就是地址,地址就是指针

地址就是内存单元的编号

指针变量是存放地址(指针)的变量

指针和指针变量是两个不同的概念

但是要注意:通常我们叙述时会把指针变量简称为指针

指针的定义

地址

内存单元的编号

从零开始的非负整数

image-20220710093119665

控制线:控制的是数据传输的方向

数据线:用来进行数据传输

地址线:用来确定是对哪部分内存单元来进行控制

image-20220710092838874

范围:4G【0–4G-1】

指针

指针就是地址,地址就是指针

指针变量就是存放内存单元的编号的变量,或者说指针变量就是存放地址的变量

指针和指针变量是两个不同的概念

但是要注意:通常我们叙述时会把指针变量简称为指针,实际他们是不同的

指针的本质就是一个操作受限的非负整数

指针的分类

基本类型指针【重点】
# include <stdio.h>

int main(void)
{
	int i = 3;
	int * p;
	int j;
	
	p = &i;
	
	printf("%d\n",p);//6487572
	printf("%d\n",&i);//6487572
	//说明p中存储的是i的地址
	
	p =  &j;
	printf("%d\n",&i);//6487572
	//改变p的值,并不影响i
	
	i = 5;
	p = &i;
	printf("%d\n",p);//6487572 
	//改变i的值并不影响p,因为p里面存放的是i的地址
	
	*p = i;
	printf("%d\n",*p);//5 
	
	return 0;
}

常见错误

*p 以p的内容为地址的变量

# include <stdio.h>

int main(void)
{
	int i = 3;
	int * p;
	int * q;
	
	p = &i;
//	*q = p;//error,从“int *”到“int”的无效转换
//	*q = *p;//error
	p = q;//error q是垃圾值,q赋给p,p也变成了垃圾值 
	
	printf("%d\n",*q);
	/*
		q的空间是属于本程序的,所以本程序可以读写q的内容,
		但如果q内部是垃圾值,则本程序将不能读写*q的内容
		因为此时*q所代表的内存单元的权限并没有分配给本程序
		所以本程序运行到14行时就会立即出错 
	*/ 
	
	return 0;
}
互换两数

image-20220710111652453

# include <stdio.h>

void huhuan_1(int a,int b);
void huhuan_2(int * p,int * q);

void huhuan_3(int * p,int * q)
{
	int t;
	
	t = *q;
	*q = *p;
	*p = t;
}

int main(void)
{
	int a = 3;
	int b = 5;
	
	huhuan_3(&a,&b);
	
//	huhuan_2(&a,&b);
//	huhuan_1(a,b);
	
	printf("a = %d, b = %d\n",a,b);
	
	return 0;
}

void huhuan_2(int * p,int * q)
{
	int * t;
	t = q;
	q = p;
	p = t;
}

void huhuan_1(int a,int b)
{
	int t;
	t = a;
	a = b;
	b = t;
	return;
}
下为huhuan_2的图示

image-20220710105048764

# include <stdio.h>

void huhuan_3(int * p,int * q)
{
	int t;
	
	t = *q;
	*q = *p;
	*p = t;
}

int main(void)
{
	int a = 3;
	int b = 5;
	
	huhuan_3(&a,&b);
	
	printf("a = %d, b = %d\n",a,b);
	
	return 0;
}

*的含义
  1. 乘法

  2. 定义指针变量

    int * p;
    //定义了一个名字叫p的变量, int *表示p只能存放int变量的地址
    
  3. 指针运算符
    该运算符放在已经定义好的指针变量的前面
    如果p是一个定义好的指针变量
    则 *p表示以p的内容为地址的变量

如何通过被调函数修改主调函数普通变量的值
  1. 实参必须为该普通变量的地址
  2. 形参必须为指针变量
  3. 在被调函数中通过
    *形参名 = 。。。
    的形式就可以修改主调函数相关变量的值
1.实参必须为该普通变量的地址(实参的‘ 地址 ’传递给形参,若为int *p和a[5]即指针和数组,则直接在实参内写上数组名(首地址)和指针变量名即可如f(p)和f(a),其他类型实参写为&a ,  &b,  &c等) 
2. 形参必须为指针变量(实参的地址传递给形参 , 形参必为指针类型即形参int * a,  int * b,  int * c 等)     
指针和数组
指针和一维数组

一维数组名

​ 一维数组名是个指针(地址)常量

​ 它存放的是一维数组第一个元素的地址

# include <stdio.h>

int main(void)
{
	int a[5];//a是数组名,5是数组的元素个数 元素就是变量 a[0] - a[4] 
//	int a[3][4];//3行4列,a[0][0] 是第一个元素,a[i][j]表示第i+1行,第j+1列
	int b[5];
	
	//a = b;//error a是常量
	printf("%#X\n",&a[0]); 
	printf("%#X\n",a); 
	printf("%#X\n",&a[1]); 

	return 0;
}

/*
输出结果为:
---------------------------
0X62FE00
0X62FE00
0X62FE04
	总结:
		一维数组名
				一维数组名是个指针常量
				它存放的是一维数组第一个元素的地址
--------------------------- 
*/

下标和指针的关系

​ 如果p是个指针变量,则 p[i] 永远等价于 *(p+i)

如果p是指针变量,则 p[ i ] 永远等价于 *(p+i)。

例如,用for循环输出一个数组时,可以这么写:printf(“%d”, (数组名 + i)); 即输出了 数组名、(数组名+1)、(数组名+2) … 其中一个,等价于printf(“%d”, 数组名[ i ])。

/*
2022年7月11日18:03:38
如果p是个指针常量
	p[i] 永远等价于 *(p+i)

而且在数组中,这个 p 不用刻意去定义,而是 数组名称
*/

#include<stdio.h>

int main(void)
{
	int a[5] = {43,21,56,23,76};

	// 一维数组名 a 表示 该数组a【0】 的地址,也就是说 a 是个指针(地址)常量 

	printf("a[3] = %d\n",a[3]);
	printf("a[3] = %d\n",*(a+3));
	
	printf("a[0] = %d\n",a[0]);
	printf("a[0] = %d\n",*a);
	
	printf("a[0] = %p\n",&a[0]);
	printf("a[0] = %p\n",&*a);
	
	printf("a[1] = %p\n",&a[1]);
	printf("a[1] = %p\n",&*(a+1));//这里的加一是因为整形变量在c语言中占4个字节,加的1也是四个字节,所以,加一就是在内存地址中加 4 刚好是下一个数组所在的内存单元的地址 
	
	printf("a[2] = %p\n",&a[2]);
	printf("a[2] = %p\n",&*(a+2));

	return 0;
}

/*输出结果是:
——————————————
a[3] = 23
a[3] = 23
a[0] = 43
a[0] = 43
a[0] = 000000000062FE00
a[0] = 000000000062FE00
a[1] = 000000000062FE04
a[1] = 000000000062FE04
a[2] = 000000000062FE08
a[2] = 000000000062FE08


——————————————
*/

对double类型的变量, p[i] 永远等价于 *(p+i)依旧适用,此时,+1会加8个内存单元。

也就是可以理解为 *(p+i)是直接跳转到下i个内存单元,而不是在内存地址上相加减。

理解数组:为什么a[3]是第四个元素,因为a[3]等价于 *(a+3),而 (a+3)表示第四个元素的地址

所以*(a+3)表示a[3]表示第四个元素

image-20220711220812196

# include <stdio.h>

int main(void)
{
	double a[6] = {5,8,6,3,4};
	int i;
	
	for(i = 0;i < 6;i++)
	{
		printf("%p\n",&a[i]);
	}
	
	printf("%lf\n",a[2]);
	printf("%lf\n",*(a+2));
	
	printf("%p\n",&a[2]);
	printf("%p\n",&*(a+2));
	
	printf("%p\n",&a[3]);
	printf("%p\n",&*(a+3));
	
	
	return 0; 
}

/*
输出结果为:
---------------------------
000000000062FDD0
000000000062FDD8
000000000062FDE0
000000000062FDE8
000000000062FDF0
000000000062FDF8
6.000000
6.000000
000000000062FDE0
000000000062FDE0
000000000062FDE8
000000000062FDE8
---------------------------- 
*/

确定一个一维数组需要几个参数?【如果一个函数要处理一个一维数组,则需要接受该数组的哪些信息】

需要两个参数:

​ 数组第一个元素的地址

​ 数组的长度

# include <stdio.h>

void f(int * pArr,int len)
{
	int i;
	
	for(i = 0;i < len;i++)
	{
		printf("%d  ",pArr[i]);
	}
	printf("\n");
}


int main(void)
{
	int a[5] = {4,5,9,87,5};
	int b[3] = {2,7,9};
	int c[100] = {46,8,31,4};
	
	f(a,5);
	f(b,3);
	f(c,100);
	
	return 0;
}

/*
输出结果为:
----------------------------------
4  5  9  87  5
2  7  9
46  8  31  4  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
------------------------------------ 
*/
/*
2022年7月11日22:21:05
一定要明白pArr[3]与主函数中的a[3]是同一个变量
注意!这里是同一个变量,而不是指向同一个变量,这里是变量,而不是地址,是取地址!
*/
# include <stdio.h>

void f(int * pArr,int len)
{
	pArr[3] = 88;
}

int main(void)
{
	int a[5] = {1,2,3,4,5};
	
	printf("%d\n",a[3]);
	
	f(a,5);
	
	printf("%d\n",a[3]);
	
	return 0;
}
/*
输出结果为:
----------------------------------
4 
---------------------------------- 
*/
# include <stdio.h>

void f(int * pArr, int len)
{
	int i;
	for (i=0; i<len; ++i)
		printf("%d  ",  pArr[i]);  //*(pArr+i) 等价于 pArr[i]  也等价于 b[i] 也等价于 *(b+i) 

	printf("\n");
}

int main(void)
{
	int b[6] = {-1,-2,-3,4,5,-6};

	f(b, 6);

	b[i]

	return 0;
}

指针变量的运算

​ 指针变量不能相加,不能相乘,也不能相除

​ 如果两个指针变量指向的是同一块连续空间中的不同存储单元,则这两个指针变量才可以相减

#include <stdio.h>

int main(void)
{
	int i = 5;
	int j = 10;
	int * p = &i;
	int * q = &j;
	// q - p没有实际意义
	 
	int a[5];
	p = &a[1];
	q = &a[4];
	
	printf("p和q所指向的单元相隔%d个单元\n",q-p);
	
	
	return 0;
}

一个指针变量到底占几个字节【非重点】

预备知识:

8bit(位)=1Byte(字节)

1024Byte(字节)=1KB

1024KB=1MB

1024MB=1GB

1024GB=1TB

​ sizeof(数据类型)

​ 功能:返回值就是 该数据类型所占的字节数

​ 例子:sizeof(int) = 4 sizeof(char) = 1

​ sizeof(double) = 8

sizeof(变量名)

功能:返回值是该变量所占的字节数

总结:

​ 一个指针变量,无论它指向的变量占几个字节,该指针变量本身只占四个字节。

​ 一个变量的地址使用该变量首字节的地址来表示

问:一个指针变量到底占几个字节?

答:因机器不同所占的字节数也有所不同。在32位的机器上,一个指针变量占4个字节,在 64 位的机器上,一个指针变量占8个字节。先看一个例子:

#include <stdio.h>

int main(void)
{
	char ch = 'A';
	int i = 99;
	double x = 66.6;
	char * p = &ch;
	int * q = &i;
	double * r = &x;
	
	printf("%d %d %d\n",sizeof(p),sizeof(q),sizeof(r));
	
	
	return 0;
} 

上述代码中,定义了3个变量,分别是char、int、double类型,还定义了3个指针变量p、q、r,分别指向char、int、double类型。然后输出这3个指针变量的值。 按理说,char占1个字节,int占4个字节,double占8个字节,当然分别指向它们的指针变量所占的字节数应该是不同的。可是,果真是这样吗?编译后运行,我们得到结果: 4 4 4 。

也就是说,分别指向3个不同类型的指针变量它们所占的字节数是相同的,都是4个字节。 这是为什么呢?

首先,我们来看一看ch 、i、x这3个变量在内存中是如何存储的。

img P指向ch,q指向i,r指向x,ch占1个字节,i占4个字节,r占8个字节,那么是不是p指向了1个字节,q指向了4个字节,r指向了8个字节呢?

当然不是。

首先要明白的是,在硬件层面上,最小的单位不是位,而是字节。所以地址不是以位来表示的,而是以字节来表示的。以上图的x变量为例。因为x占了8个字节,所以在地址编号上,它有8个编号。(一个字节一个编号嘛),既然r指向了x,那么r是不是同时指向了这8个编号呢?

不是。r是指向了这8个编号当中的第一个编号。即x的首地址。我们一般都是以一个变量的首地址来表示整个变量的。同理,q指向了i,但它并不是同时指向了4个字节,只是指向了i的首地址。ch就不用说了,因为它只占一个字节。

为什么r明明只指向了1个字节,但是它却可以表示8个字节?为什么q只指向了1个字节,但是它却可以表示4个字节?我为什么不能说r表示的是从它指向的那个字节开始的4个?我为什么不能说q表示的是从它指向的那个字节开始的2个?这是由什么来决定的呢?

答案是:这是由变量的类型来决定的。

请看这一句:double * r =&x;

因为我们在声明指针变量r时,是把它声明为一个double类型的变量。Double类型的变量在内存中就是占8个字节的,所以,虽然r指向的是x的首地址,但编译器会认为r表示的是从它指向的那个地址开始的8个字节所代表的变量。

同理,因为q是一个int类型的指针变量,所以编译器会认为q表示的是从它指向的那个地址开始的4个字节所代表的变量。

接下来:为什么p、q、r中只存放了1个字节的地址,但却都占了4个字节呢?

这是因为第1个字节的地址只是一个编号,指针变量是存储那个编号的。为了存储那个编号,需要动用4个字节的存储空间。

听起来似乎有点难以理解。你明明只有1个字节,为什么要用4个字节的空间去存储?这样不是浪费了3个字节吗?

内存当中有很多的编号,代表着不同的地址。越往后的地址编号越大。有的单元当中可能只存储一个字节,但因为这个单元的位置比较靠后,所以编号当然就大了。

可是,为什么是4个字节呢?

img

在32位的计算机中,CPU是以32根地址总线来控制与内存的数据交换的。1根地址总线可以表示2种状态(1个0和1个1),2根地址总线可以表示4种状态,3根地址总线可以表示8种状态,32根地址总线总共可以表示 2的32 次方种状态,也就是说他能够确定 2的32次方 个单元或者说它有 2的32次 方个编号。

因此,从第1个编号一直到最后一个编号,每一个编号都需要用32根线来表示,只不过每一个编号都需要用32根线的不同状态来表示。比如编号为0的单元其实是表示为00000…000000,共32个0,编号为1的单元其实是表示为00000…000001,共31个0和1个1。最后一个编号其实就是表示为11111…11111,共32个1。所以,从第1个字节一直到最后一个字节都需要用32位来表示。而一个字节是8位,即每个单元要用32/8=4个字节来表示。

指针变量是保存一个变量地址的变量,它里面保存的是一个特殊的东西:地址,即内存单元。一个内存单元要用32个状态来表示,(32个0和1),一个0或1占用1个位,8个位是一个字节,所以当然要占用32/8=4个字节的空间。

指针和二维数组
指针和函数
指针和结构体【重点】
多级指针
# include <stdio.h>

int main(void)
{
    int i = 10;
    int * p = &i;
    int ** q = &p;
    int *** r = &q;
    
    //r = &p;//因为r是int *** 类型,r只能存放 int ** 类型变量的地址
    printf("i = %d\n",***r);
     
    return 0;
}

image-20220713170959106

# include <stdio.h>

void f(int ** q)
{
    //q 是 p 的地址,*q 就是 p
}
void g()
{
    int i = 10;
    int * p = &i;
    
    f(&p);//p是int * 类型,&p是int ** 类型
}

int main(void)
{
    g();
    
    return 0;
}

专题:动态内存分配

传统数组的缺点
  1. 数组长度必须事先指定,且只能是常整数,不能是变量
    例子

    int a[5];//OK
    int len = 5;int a[len];//error
    
  2. 传统形式定义的数组,该数组的内存程序员无法手动释放
    在一个函数运行的期间,系统为该函数中数组所分配的空间会一直存在,直到该函数运行完毕时,数组的空间才会被系统释放

  3. 数组的长度一旦定义,其长度就不能再次更改
    数组的长度不能在函数运行的过程中动态的扩充或缩小

  4. 传统方式定义的数组不能跨函数使用
    A函数定义的数组,在A函数运行期间可以被其它函数使用,但A函数运行完毕之后,A函数中的数组将无法在其它函数使用

为什么需要动态分配内存

动态数组很好的解决了传统数组的4个缺陷

传统数组也叫静态数组

image-20220712183915278

/*
	2022年7月12日18:30:50 
	malloc 是 memory(内存) allocate(分配)的缩写
*/

#include <stdio.h>
#include <malloc.h>

int main(void)
{
	int i = 5;//分配了4个字节,静态分配
//	int * p = (int)malloc(4);//从'void*'转换为'int'失去精度[-fpermissive]
	int * p = (int *)malloc(4); 
	/*
		1.要是用malloc函数,必须添加malloc.h这个头文件
		2.malloc函数 只有一个形参,并且形参是整形
		3.形参(4)表示请求系统为本程序分配4个字节 
		4.malloc函数只能返回第一个字节的地址,(int *)表示将分配的4个字节强制转换为 int *类型
		5.13行分配了8个字节,p占4个字节,p所指向的内存也占4个字节
		6.p本身所占的内存是静态分配的,p所指向的内存是动态分配的 
	*/ 
	
	*p = 5; //*p 代表的就是一个int变量, 只不过*p这个整型变量的内存分配方式和11行的i变量的分配方式不同
	printf("%d\n",*p);
	free(p); //freep(p)表示把p所指向的内存给释放掉  p本身的内存是静态的,不能由程序员手动释放,p本身的内存只能在p变量所在的函数运行终止时由系统自动释放 
	printf("%d\n",*p);
	printf("同志们好!\n");
	
	return 0;
} 
/*
输出结果为:
--------------------------------
5
10056032
同志们好!
-------------------------------- 
*/

image-20220712184149297

image-20220712185932987

# include <stdio.h>
# include <malloc.h>

void f(int * q)
{
	*q = 200;
	free(q);//把q所指向的内存单元释放,会导致17行代码出错 
}

int main(void)
{
	int * p = (int *)malloc(sizeof(int));//sizeof(int)返回值是int所占的字节数 
	*p = 10;
	
	printf("%d\n",*p);
	f(p);
	printf("%d\n",*p);
	
	return 0;
} 
动态内存分配举例_动态数组的构造

image-20220713095025757

image-20220713095903713

# include <stdio.h>
# include <malloc.h>

int main(void)
{
	int a[5];//如果int类型占4个字节的话,本数组占20个字节,每四个字节被当做了一个int变量来实用 
	int len;
	int * pArr;
	
	printf("请输入要定义的数组的长度:\n");
	scanf("%d",&len);//5

	pArr = (int *)malloc(4*len);//本行动态的构造了一个一维数组,该一维数组的长度是len,该数组的数组名是pArr,该数组的每个元素是int类型,类似于 int a[len];
	/*
		1.pArr存放的是第一个字节的地址,但是又由于pArr本身是int *类型,
		  所以pArr指向的是前四个字节!
		  也就是说,如果 强制转换类型 和 pArr 是double *类型的话。pArr存放的就是double类型的首地址,pArr指向的就是前8个字节,是char *;类型的话,指向的就是前1个字节
		2.*(pArr + 1)指向的就是后4个字节(8个,1个)
		参考为什么需要动态分配内存的第一张图,数据类型决定指向多么大的整块的地址 
	*/ 
	
	return 0;
}

对动态数组的操作

# include <stdio.h>
# include <malloc.h>

//对数组进行赋值 
void Valuation(int * f,int len)
{
	int i;
	for(i = 0;i < len;i++)
	{
		scanf("%d,",&f[i]);//以逗号隔开,这里的逗号叫非输入控制符
	}
}

//对数组进行输出
void outPut(int * g,int len)
{
	int i;
	for(i = 0;i < len;i++)
	{
		printf("%d\n",g[i]);
	}
}

int main(void)
{
	int a[5];//如果int类型占4个字节的话,本数组占20个字节,每四个字节被当做了一个int变量来实用 
	int len;
	int * pArr;
	
	printf("请输入要定义的数组的长度:\n");
	scanf("%d",&len);//5

	pArr = (int *)malloc(4*len);//本行动态的构造了一个一维数组,该一维数组的长度是len,该数组的数组名是pArr,该数组的每个元素是int类型,类似于 int a[len];
	/*
		1.pArr存放的是第一个字节的地址,但是又由于pArr本身是int *类型,
		  所以pArr指向的是前四个字节!
		  也就是说,如果 强制转换类型 和 pArr 是double *类型的话。pArr存放的就是double类型的首地址,pArr指向的就是前8个字节,是char *;类型的话,指向的就是前1个字节
		2.*(pArr + 1)指向的就是后4个字节(8个,1个)
		参考为什么需要动态分配内存的第一张图,数据类型决定指向多么大的整块的地址 
	*/ 
	
	//上面已经定义好了一个动态数组,接下来对它进行操作(次数运用函数进行赋值和输出)
	printf("请输入数组的%d元素\n,以英文,隔开",len);
	
	Valuation(pArr,len);
	
	outPut(pArr,len);
	
	free(pArr);
	
	outPut(pArr,len);//释放内存后再次输出动态数组中的值将是垃圾值 
	return 0;
}
动态内存和静态内存的比较

静态内存是由系统自动分配,由系统自动释放

静态内存是在栈分配的

main调用f()函数,就是为f()函数在栈中分配存储空间,而静态内存为什么不能跨函数使用呢?

因为调用的这个函数一终止,为该函数分配的内存空间就会释放,也就是出栈了。image-20220713165402538

动态内存是由程序员手动分配,手动释放

动态内存是在堆(堆排序)分配的

跨函数使用内存的问题
#include <stdio.h>

void f(int ** q)//q是一个指针变量,无论q是什么类型的指针变量,都只占4个字节 
{
	int i = 5;
	//*q 等价于 p 
	*q = &i;//等价于 p = &i; 
}

int main(void)
{
	int * p;
	
	f(&p);
	
	printf("%d\n",*p);//此处无语法错误,但是有逻辑错误
	//f函数中的i在f函数调用完之后,内存单元已经释放,此时输出*p将指向的是一个垃圾值 
	
	return 0;
}

跨函数使用内存(在被调函数中使用动态分配内存)

#include <stdio.h>
#include <malloc.h> 

void f(int ** q)
{
//	q = (int **)malloc(sizeof(int));//sizeof(数据类型) 返回值是该数据类型所占的字节数 
	/*
	q存放的是p的地址,*q就是p,**q就是*p,
	动态的分配了4个字节给 int ** 类型 
	(int*)malloc(sizeof(int)) 将申请得到的空间地址转换成了int类型空间地址
	而无法申请空间地址转换为 int * 类型空间地址 
	*/
		*q = (int *)malloc(sizeof(int));//sizeof(数据类型) 返回值是该数据类型所占的字节数 

	**q = 5;//等价于*p = 5; 
}

int main(void)
{
	int * p;
	
	f(&p);
	
	printf("%d\n",*p);
	
	return 0;
}

结构体

为什么需要结构体

为了表示一些复杂的事物,而普通的基本类型无法满足实际要求。

# include <stdio.h>

struct Student
{
	int age;
	float score;
	char sex;
};

int main(void)
{
	struct Student st = {80, 66.6, 'F'};	
/*	int age;
	float score;
	char sex;
	int age2;
	float score2;
	char sex2;
*/
	return 0;
}

什么叫结构体

把一些基本类型数据组合在一起形成的一个新的复合数据类型,这个叫做结构体

如何定义结构体

3种方式,推荐使用第一种

#include <stdio.h>

struct student
{
	int age;
	char sex;
	float score;
};//定义了一个新的数据类型,相当于int 此处并没有定义变量 

int main(void)
{
	
	return 0;
}

下面的两种定义

# include <stdio.h>

//第二种方式
struct Student2
{
	int age;
	float score;
	char sex;
} st2;

//第三种方式
struct 
{
	int age;
	float score;
	char sex;
} st3;

int main(void)
{
	struct Student st = {80, 66.6, 'F'};	

	return 0;
}

怎样使用结构体变量

赋值和初始化

定义的同时可以整体赋初值

如果定义完之后,则只能单个的赋初值

#include <stdio.h>

//第一种方式
struct student
{
	int age;
	char sex;
	float score;	
};

int main(void)
{
	struct student st = {80,'F',66.6F 
	};
	struct student st2;
	st2.age = 23;
	st2.score = 359;
	st2.sex = 'F';
	
	printf("%d %f %c\n",st2.age,st2.score,st2.sex); 
	
	return 0;
}
如何取出结构体变量中的每一个成员【重点】
  1. 结构体变量名.成员名
  2. 指针变量名 -> 成员名(第二种方式更常用)
    *指针变量名 -> 成员名 在计算机内部会被转化成 (指针变量名).成员名 的方式来执行,所以说这两种方式是等价的。
#include <stdio.h>

struct Student
{
	int age;
	float score;
	char sex;
};

int main()
{
	struct Student st = {80,66.6F,'F'
	};//初始化的同时赋初值
	
	struct Student * pst = &st;// pst是 struct Student * 类型,所以必须是&st;
	
	st.age = 88;//第一种方式
	pst->score = 56.6F;//第二种方式
    //66.6默认为double类型,如果需要定义为float类型需要加f或F 
	printf("%d %f",pst->age,st.score);
	
	return 0;
}
/*88 56.599998*/
  1. *pst->age 在计算机内部会被转换为(pst).age,没有什么为什么,这就是->的含义,也是一种硬性规定。
  2. *所以 pst->age 等价于 (pst).age 也等价于 st.age
  3. *我们之所以知道 pst -> age等价于 st.age,是因为pst -> age是被转化成了(pst).age来执行
  4. pst->age的含义
    pst 指向的那个结构体变量中的 age 这个成员

结构体变量和结构体指针变量作为函数参数传递的问题

内存详解【重点!】

image-20220714185344439

double x = 6.6;

double * p;

p = &x;

定义了一个double类型的变量,分配了8个字节(一个字节有8位:因为是128个字符,第128是100000000,【System/360】奠定了字符存储单位采用8位长度的基础,这就是【1字节=8位】的由来。);

又定义了一个double * 类型的变量p,系统分配了4个字节(因为*类型就是用来存放地址的,在32位电脑中,一共有32跟地址总线,也就是有32位数字来表示一个地址单元,8位一个字节,故而分配了4个字节给了p);

**把x的地址给了p,p只存放了x的首地址,而p是double * ,类型,故系统就知道了p指向的是首地址开始的8个字节(一整块儿),如果x是int类型,p也是int *类型,那么p存放完x的首地址之后,指向之后的4个字节,而 (p + 1)则是指向下4个字节,也是因为p是int 类型。

推荐使用结构体指针变量作为函数参数来传递

/*
	2009年11月24日9:17:43
	老师代码
	通过函数完成对结构体变量的输入和输出
*/


# include <stdio.h>
# include <string.h>

struct Student
{
	int age;
	char sex;
	char name[100];
}; //分号不能省

void InputStudent(struct Student *);
void OutputStudent(struct Student ss);
int main(void)
{
	struct Student st;  //15行

	InputStudent(&st); //对结构体变量输入  必须发送st的地址
//	printf("%d %c %s\n", st.age, st.sex, st.name);
	OutputStudent(st); //对结构体变量输出  可以发送st的地址也可以直接发送st的内容

	return 0;
}

void OutputStudent(struct Student ss)
{
	printf("%d %c %s\n", ss.age, ss.sex, ss.name);
}

void InputStudent(struct Student * pstu) //pstu只占4个字节
{
	(*pstu).age = 10;
	strcpy(pstu->name, "张三");//strcpy把含有'\0'结束符的字符串复制到另一个地址空间,返回值的类型为char*。
	pstu->sex = 'F';	
}

/*
//本函数无法修改主函数15行st的值 所以本函数是错误的
void InputStudent(struct Student stu)
{
	stu.age = 10;
	strcpy(stu.name, "张三");  //不能写成 stu.name = "张三";
	stu.sex = 'F';
}
*/
/*
2022年7月14日21:31:50
自己代码
*/

# include <stdio.h>
# include <string.h>

void IntputStudent1(struct Student stu); 
void OutputStudent1(struct Student stu);

void IntputStudent2(struct Student * stu); 
void OutputStudent2(struct Student stu);

struct Student{
	int age;
	double score;
	char sex;
	char name[7]; 
};//至少占 20 个字节 

int main(void)
{
	struct Student st;
	
	IntputStudent2(&st);
	OutputStudent2(st);
	
	return 0;
}

void IntputStudent2(struct Student * stu)//形参是struct Student * 类型,存放的是实参的首地址,占4个字节 
{
	(*stu).age = 20;//stu存放的是st的地址,*stu就是st本身,故 (*stu).age 等价于 st.age 
	strcpy((*stu).name ,"张智超");//stu.name = "张智超";
	(*stu).score = 389;
	(*stu).sex = 'N';
}

//void IntputStudent2(struct Student * stu)//形参是struct Student * 类型,存放的是实参的首地址,占4个字节 
//{
//	stu->age = 20;
//	strcpy(stu->name ,"张智超");//stu.name = "张智超";
//	stu->score = 389;
//	stu->sex = 'N';
//}

void OutputStudent2(struct Student stu)
{
	printf("%d %lf %c %s",stu.age,stu.score,stu.sex,stu.name);
}

void IntputStudent1(struct Student stu)//形参是新定义的数据类型,至少占20个字节,函数运行结束之后空间释放,就不行了,故得考虑用指针 
{
	stu.age = 20;
	strcpy(stu.name ,"张智超");//stu.name = "张智超";
	stu.score = 389;
	stu.sex = 'N';
}

void OutputStudent1(struct Student stu)
{
	printf("%d %lf %c %s",stu.age,stu.score,stu.sex,stu.name);
}

发送地址还是发送变量

/*
	2009年11月24日9:17:43
	示例:
		发送地址还是发送内容
	目的:
		指针的优点之一:
			快速的传递数据,
			耗用内存小
			执行速度快
*/


# include <stdio.h>
# include <string.h>

struct Student
{
	int age;
	char sex;
	char name[100];
}; //分号不能省

void InputStudent(struct Student *);
void OutputStudent(struct Student *);
int main(void)
{
	struct Student st ;  //15行
	//printf("%d\n", sizeof(st));

	InputStudent(&st); //对结构体变量输入  必须发送st的地址
	OutputStudent(&st); //对结构体变量输出  可以发送st的地址也可以直接发送st的内容 但为了减少内存的耗费,也为了提高执行速度,推荐发送地址

	return 0;
}

void OutputStudent(struct Student *pst)
{
	printf("%d %c %s\n", pst->age, pst->sex, pst->name);
}

void InputStudent(struct Student * pstu) //pstu只占4个字节
{
	(*pstu).age = 10;
	strcpy(pstu->name, "张三");
	pstu->sex = 'F';	
}

/*
//本函数无法修改主函数15行st的值 所以本函数是错误的
void InputStudent(struct Student stu)
{
	stu.age = 10;
	strcpy(stu.name, "张三");  //不能写成 stu.name = "张三";
	stu.sex = 'F';
}
*/

结构体变量的运算

结构体变量不能相加,不能相减,也不能相互乘除

但结构体变量可以相互赋值

struct Student{
    int age; 
	char sex: 
	char name[100J;
}; //分号不能省

struct Student st1, st2; 
stl+st2 st1*st2 stl/st2 都是错误的
stl = st2 或者st2 = stl 都是正确的
举例(学生管理系统)

动态构造存放学生信息的结构体数组

动态构造一个数组,存放学生的信息

然后按分数排序输出

image-20220714223631576

# include <stdio.h>
# include <malloc.h>

struct Student
{
	int age;
	float score;
	char name[100];
};

int main(void)
{
	int len;
	struct Student * pArr;
	int i, j;
	struct Student t;
	

	//动态的构造一维数组
	printf("请输入学生的个数:\n");
	printf("len = ");
	scanf("%d", &len);
	pArr = (struct Student *)malloc(len * sizeof(struct Student));
	
	//输入
	for (i=0; i<len; ++i)
	{
		printf("请输入第%d个学生的信息:\n", i+1);
		printf("age = ");
		scanf("%d", &pArr[i].age);

		printf("name = ");
		scanf("%s", pArr[i].name);  //name是数组名,本身就已经是数组首元素的地址, 所以pArr[i].name 不能改成 &pArr[i].name

		printf("score = ");
		scanf("%f", &pArr[i].score);
	}

	//按学生成绩升序排序 冒泡算法
	for (i=0; i<len-1; ++i)
	{
		for (j=0; j<len-1-i; ++j)
		{
			if (pArr[j].score > pArr[j+1].score) //>升序 <降序
			{
				t = pArr[j];
				pArr[j] = pArr[j+1];
				pArr[j+1] = t;
			}
		}
	}

	printf("\n\n学生的信息是:\n");
	//输出
	for (i=0; i<len; ++i)
	{
		printf("第%d个学生的信息是:\n", i+1);
		printf("age = %d\n", pArr[i].age);
		printf("name = %s\n", pArr[i].name);  //name是数组名,本身就已经是数组首元素的地址, 所以pArr[i].name 不能改成 &pArr[i].name
		printf("score = %f\n", pArr[i].score);
	
		printf("\n");
	}

	return 0;
}数据类型:pArr结构体指针变量,  pArr[i]结构体变量,  pArr[i].age结构体变量的整型元素,  pArr[i].name结构体变量的字符数组元素
/*
	2022年7月15日11:21:09
	用函数实现学生管理系统
*/
# include <stdio.h>
# include <malloc.h>

void InPut(struct Student * pAr,int len);
void OutPut(struct Student * pAr,int len);
void sort(struct Student * pAr,int len);

struct Student
{
	int age;
	float score;
	char name[20];
};

int main(void)
{
	//动态构造一维数组
	int i,j;
	int len;
	printf("请输入学生的个数\n");
	scanf("%d",&len);
	struct Student * pArr;//int * pArr
	pArr = (struct Student *)malloc(len*sizeof(struct Student));

	InPut(pArr,len);//对数组进行输入
	sort(pArr,len);//对数组依据学生成绩排序
	OutPut(pArr,len);//对数组内容进行输出

	return 0;
}

void InPut(struct Student * pAr,int len)
{
	int i;
	for(i = 0;i < len;i++)
	{
		printf("请输入第%d个学生的年龄:\n",i+1);
		scanf("%d",&pAr[i].age);
		printf("请输入第%d个学生的成绩:\n",i+1);
		scanf("%f",&pAr[i].score);
		printf("请输入第%d个学生的名字:\n",i+1);
		scanf("%s",pAr[i].name);
	}
}
void OutPut(struct Student * pAr,int len)
{
	int i;
	printf("\n\n数据库中存储的学生信息为:\n");
	for(i = 0;i < len;i++)
	{
		printf("第%d位学生的年龄为\n",i+1);
		printf("%d\n",pAr[i].age);
		printf("第%d个学生的成绩为:\n",i+1);
		printf("%f\n",pAr[i].score);
		printf("第%d个学生的名字为:\n",i+1);
		printf("%s\n",pAr[i].name);
		printf("\n");
	}
}
void sort(struct Student * pAr,int len)
{
	struct Student t;
	int i,j;
	for(i = 0;i < len-1;i++)
	{
		for(j = 0;j < len-i-1;j++)
		{
			if(pAr[j].score > pAr[j+1].score)//升序
			{
				t = pAr[j];
				pAr[j] = pAr[j+1];
				pAr[j+1] = t;
			}
		}
	}
}

枚举

什么是枚举

把一个事物所有可能的取值一一列举出来

怎么使用枚举

//这里只是定义了一个(枚举类型enumweekday),就像int类型,char类型一样 
enumweekday 
{ 
 monday, tuesday, wednesday, tursday, friday, saturday, sunday 
}; //注意:分号不能省略 
int main(void) 
{ 
 enumweekday day; 
 day = wednesday;//右边只能写(枚举类型enumweekday)中的,其物理内部默认从零开始 
 printf("%d\n", day);//结果为2。若将enumweekday中monday改为monday=4,运行结果为6 
}

枚举的优缺点

代码更安全

书写麻烦

专题

进制转换

image-20220715111456464

image-20220715111544855

image-20220715111634772

image-20220715111721395

image-20220715111757059

image-20220715111807806

image-20220715111819033

补码:

原码

也叫 符号 - 绝对值码

最高位 0 表示正 1 表示负,其余二进制位是该数字的绝对值的二进制位

原码简单易懂

加减运算复杂

存在加减乘除四种运算,增加了 CPU 的复杂度

零的表示不唯一

反码

反码运算不便,也没有在计算机中应用

移码

移码表示数值平移n 位,n 称为移码量

移码主要用 于浮点数的阶码的存储

补码

己知十进制求二进制

求正整数的二进制:

​ 除2取余,直至商为零,余数逆排

求负整数的二进制

​ 先求与该负数相对应的正整数的二进制代码,然后将

​ 所有位取反,末尾加1.不够位数肘,左边补1

求零的二进制

​ 全是零

己知二进制求十进制

​ 如果首位是0,则表明是正整数,按普通方法来求

​ 如果首位是1则表明是负整数

​ 将所有位取反,末尾加1,所得数字就是该负数的绝对值

​ 如果全是零,则对应的十进制数字就是零

  • 36
    点赞
  • 140
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值