C语言的学习(主要是函数和指针)

由于函数和指针不太懂,所以重新学习了C语言,这篇文章是自己学习的笔记,也是学习过程的记录。

求分数的等级
/*
2023年5月27日16:56:02
功能:求分数等级
目的:学习 if-else 
*/

# include <stdio.h>

int main(void)
{
	float score;
	printf("请输入你的分数:");
	scanf("%f",&score);
	if(score>100)
		printf("输入成绩有误!\n");
	else if (90<=score && score <=100) //不能写成 90<=score<=100  这个是逻辑值 结果永远是1
		printf("优秀\n");
	else if (80<=score && score <90)
		printf("良好\n");
	else if (60<=score && score <80)
		printf("及格\n");
	else if (score<60)
		printf("不及格\n"); 
	else
		printf("输入分数过低\n");
	return 0; 
 } 
 /* Dev-C++运行结果 
 请输入你的分数:70
及格
 */ 
互换两个数字
/*
2023年5月27日16:56:02
功能:互换两个数字 
目的:
*/

# include <stdio.h>

int main(void)
{
	int i = 3;
	int j = 5;
	int t; //定义临时变量
	t = i;
	i = j;
	j = t;
	printf("i=%d,j=%d",i,j); 
	return 0; 
 } 
 /* Dev-C++运行结果 
i=5,j=3
 */ 
对任意三个数进行排序(冒泡排序)
/*
2023年5月27日18:15:06 
功能:对任意三个数字进行排序 
目的:初识冒泡排序 、
思想:a b c d e f 
   	a与b进行比较   若a>b,则ab互换 
	依次与后面每个数进行比较    最终a为最大的数  
	b与后面的每个数进行比较  依次…… 
*/

# include <stdio.h>

int main(void)
{
	int a,b,c;
	int t; 
	printf("请输入三个整数(中间以空格隔开):");
	scanf("%d %d %d",&a,&b,&c);
	//编码完成时a是最大值  b是中间值 c是最小值 
 	if(a<b)
 	{
 		t=a;
 		a=b;
 		b=t;
	}
	if(a<c)
	{
		t=a;
		a=c;
		c=t;
	}
	if(b<c)
	{
		t=b;
		b=c;
		c=t;
	}
	printf("%d %d %d\n",a,b,c);
	return 0;
}
 /* Dev-C++运行结果 
请输入三个整数(中间以空格隔开):1 2 3
3 2 1
*/ 
if常见问题解析
if(表达式1if(表达式1;  //如果这里加分号,则会导致程序
	A;                	A;		  //编译到3行时出错,没有else开
else				else		  //头的语句
	B;					B;
是正确的			是错误的

if(表达式1)					if(表达式1)
	A;								A;
else if (表达式2)				else if (表达式2)
	B;								B;
else if (表达式3)				else if (表达式3)		
	C;								C;
else 						//这样写语法不会出错,但逻辑上有漏洞
	D;
// 即使表达式1和2都成立,也只会执行A语句
if(表达式1)					if(表达式1)		
	A;								A;					
else if (表达式2)				else if (表达式2)			
	B;								B;					
else if (表达式3)				else if (表达式3)			
	C;								C;					
else (表达式4)					else (表达式4);			
	D;								D;
/*  这样写是不对的,正确的
写法是:					  这样写语法不会出错,但逻辑出错
要么去掉7行的(表达式4)		  else (表达式4);
要么在7行的else 后面加if				D;
							 等价于
							 else 
							       (表达式4);
							 D;   */

循环

定义:某些代码会被重复执行

  • for

    for(1;2;3)
    	语句A;
    /* 先执行1 再执行2 然后执行语句A 然后执行3 依次2 语句A 3
       3执行完后 标志1次循环结束
       */
    
    /*
    2023年5月27日20:56:06
    功能:for循环最简单的用法 
    目的:
    */
    
    # include <stdio.h>
    
    int main(void)
    {
    	int i;
    	int sum = 0;
    	for(i=1; i<10; i+=2)//i+=2 等价于 i=i+2
    	{
    		sum = sum + i;
    	 } 
    	 printf("i = %d\n",i);
    	 printf("sum = %d\n",sum);
    	return 0;
    }
     /* Dev-C++运行结果 
    i = 11
    sum = 25
    */ 
    
强制类型转换
/*
2023年5月27日22:20:11
功能:强制类型转换 
心得:试数详细步骤:
1—> i=1 i<=100 成立
	sum=0+1/1.0  i++ i=2
2—> i=2 i<=100 成立
	sum=0+1/1.0+1/2.0 i++ i=3
3—> i=3 i<=100 成立
	sum=0+1/1.0+1/2.0+1/3.0  i++ i=4
*/
# include <stdio.h>

int main(void)
{
	int i;
	float sum = 0;
	for(i=1; i<=100; i++)
	{
		sum = sum + 1 / (float)(i);
		// sum = sum + (float)(1/i) 这样是不对的 
		//更简单的写法是:sum=sum + 1.0/i 
	}
	printf("sum = %f\n",sum);
	return 0;
}
 /* Dev-C++运行结果 
sum = 5.187378
*/ 
浮点数的存储所带来的问题

float 和double都不能保证可以精确的存储一个小数

举例:有一个浮点型变量x,如何判断x的值是否是零

if(|x-0.000001| < 0.000001)
	是零
else
	不是零
for和if的嵌套使用四个习题

求1到100之间的奇数之和

/*
2023年5月28日16:26:37
功能:求1到100之间的奇数之和 
目的:
*/

# include <stdio.h>

int main(void)
{
	int i;
	int sum=0;
	for(i=1; i<=100; ++i){
		if(i%2 != 0)
		sum = sum + i; 
	}
	printf("奇数和为:%d",sum);
	return 0;
}
 /* Dev-C++运行结果 
奇数和为:2500
*/ 

求1到100之间的奇数的个数

/*
2023年5月28日16:35:24
功能:求1到100之间的奇数的个数 
目的:
*/

# include <stdio.h>

int main(void)
{
	int i;
	int cnt=0;
	for(i=1; i<=100; ++i){
		if(i%2 != 0)
		cnt++; 
	}
	printf("奇数个数为:%d",cnt);
	return 0;
}
 /* Dev-C++运行结果 
奇数个数为:50
*/ 

求1到100之间的奇数的平均值

/*
2023年5月28日16:46:34
功能:求1到100之间的奇数的平均值 
目的:
*/

# include <stdio.h>

int main(void)
{
	int i;
	float avg;
	int cnt=0;
	int sum=0; 
	for(i=1; i<=100; ++i){
		if(i%2 != 0)
		{
			sum += i;
			cnt++;
		}
	}
	avg = 1.0*sum/cnt;  //1.0默认是double类型  
	printf("奇数的平均值为:%f",avg);
	return 0;
}
 /* Dev-C++运行结果 
奇数的平均值为:50.000000
*/ 

求1到100之间的奇数之和,再求1到100之间的偶数之和

/*
2023年5月28日16:25:32
功能:求1到100之间的奇数之和,再求1到100之间的偶数之和 
目的:
*/

# include <stdio.h>

int main(void)
{
	int i;
	int sum1=0;
	int sum2=0;
	for(i=1; i<=100; i++){
		if(i%2 != 0){
			sum1 = sum1 + i;
		}
		else
			sum2 = sum2 + i;
	}
	printf("奇数和为:%d,偶数和为:%d\n",sum1,sum2);
	return 0;
}
 /* Dev-C++运行结果 
奇数和为:2500,偶数和为:2550
*/ 
多个for循环的嵌套使用

image-20230528171739814

从最外面的循环开始执行,先执行 1 ,再执行条件判断语句 2。如果 2 成立,则依次执行语句 4 和 5,如果 5 成立 则执行 A和 6,如果 5不成立,则结束第二个 for 循环,去执行3,再执行2,以此循环。直到 2 为假时,结束循环。

整体是两个语句,第1、2、3行是一个语句,第4行是第二个语句

for (1;2;3)
	for (4;5;6)
	{
	 	A;
		 B;
	}

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

整体是一个语句

进制

  1. 什么叫进制

    逢n进一

  2. 把r进制转化为十进制

    从左到右依次把数字乘以r对应的幂指数。

  3. 十进制转成r进制

    除r取余,直至商0,余数倒序排列。

  4. 不同进制所代表的的数值之间的关系。十进制的 3981 转化为十六进制是 F8D。十进制的 3981 和 十六进制的 F8D 所代表的本质上都是同一个数

一些琐碎的运算符知识

1.自增[或者自减]
  • 分类

    • 前自增: ++i
    • 后自增:i++
  • 前自增和后自增的异同:

    • 相同:

      最终都是i的值加1

    • 不同:

      前自增整体表达式的值是i加1之后的值
      后自增整体表达式的值是i加1之前的值

  • 为什么会出现自增

    • 代码更精练
    • 自增的速度更快
  • 学习自增要明白的几个问题

    • 编程时应该尽量不要体现自增和自减的区别。或者说编程时尽量屏蔽掉前后自增和自减的差别。
    • i++ 和 ++i 单独成一个语句,不要把它作为一个完成复合语句的一部分来使用。或者说,自增表达式最好不要作为一个更大的表达式的一部分来使用。
    • 如:写成 int m = i++ + ++i + i + i++; 这么写,不但是不规范的代码,而且是不可移植的代码。不可移植是因为,不同机器上对于这个语句的执行顺序有区别。顺序点有三个 , 和 () 以及 ; 。 printf(“%d %d %d”, i++, ++i, i); 也是不推荐,原因同上。
/*
2023年5月29日11:58:30
功能:自增,自减 
*/

# include <stdio.h>

int main(void)
{
	int i;
	int j;
	int k;
	int m;
	
	i=j=3;
	k=++i;
	m=j++;
	  
	printf("i=%d, j=%d, k=%d, m=%d\n",i,j,k,m);
	return 0;
}
 /* Dev-C++运行结果 
i=4, j=4, k=4, m=3
总结:前自增:++i	后自增:i++ 
相同:最终都是i的值加1 
不同:前自增整体表达式的值是i加1之后的值 
	后自增整体表达式的值是i加1之前的值
*/ 
2.三目运算符
  • 格式

    • ​ A ?B : C

      等价于

      ​ if (A)

      ​ B;

      ​ else

      ​ C

/*
2023年5月29日12:20:38
功能:三目运算符
*/

# include <stdio.h>

int main(void)
{
	int i;
	
	i = (3>2 ? 5 : 1); //3>2成立是5 否则是1 
	printf("%d\n",i); 
	return 0;
}
 /* Dev-C++运行结果 
5
*/ 
3.逗号表达式
  • 格式

    (A, B, C, D)

  • 功能:

    从左到右执行

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

/*
2023年5月29日12:25:26 
功能:逗号表达式 
*/

# include <stdio.h>

int main(void)
{
	int i;
	int j = 2;
	
	i = (j++,++j,j+2,j-3);  // j=3,++j后j=4,j+2后的值是6 
	printf("%d\n",i); 		//但是j还是4,j-3后j是1 
	return 0;
}
 /* Dev-C++运行结果 
61
*/ 

while

1.执行顺序

​ 格式:

​ while (表达式)

​ 语句;

2.与for的相互比较

​ for和while相互比较

	for (1;2;3)
		A;
	等价于
	1;
	while (2)
	{
		A;
		3;
	} 

求回文数

/*
2023年5月29日16:44:48
功能:求回文数 12321为回文数
试数 : (1)m=1234  成立 sum=0+1234%10=4      m=1234/10=123
		(2)m=123  成立 sum=4*10+123%10=43   m=123/10=12 
		(3)m=12   成立 sum=43*10+12%10=432  m=12/10=1
		(4)m=1    成立 sum=432*10+2%10=4321 m=1/10=0
		(5)m=0    不成立
		最终sum = 4321
*/

# include <stdio.h>

int main(void)
{
	int val; //存放待判断的数
	int m;
	int sum = 0;
	printf("请输入您需要判断的数:");
	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;
}
 /* Dev-C++运行结果 
请输入您需要判断的数:1234
No!
*/ 

用递归计算斐波拉契序列第n项的值

/*
2023年5月29日16:44:48
功能:用递归计算斐波拉契序列第n项的值
		f(0) = 0; f(1) = 1; f(2) = 1; f(3) = 2
		f(n) = f(n-1) + f(n-2)  1、1、2、3、5、8 
试数 : (1)n=5 i=3 成立  f3=1+1=2 f1=1 f2=2 i++
 		(2)    i=4 成立  f3=1+2=3 f1=2 f2=3 i++
 		(3)    i=5 成立  f3=2+3=5 f1=3 f2=5 i++
      	(4)	   i=6 成立  f3=3+5=8 f1=5 f2=8 i++
      		   i=7 不成立 
*/

# include <stdio.h>

int main(void)
{
	int n;
	int f1,f2,f3;
	int i;
	f1 = 1;
	f2 = 1;
	printf("请输入您需要求的想的序列:");
	scanf("%d",&n);
	if (1==n || n==2)
	{
		f3 = 1;
	}
	else
	{
		for (i=3; i<=n; ++i)
		{
			f3 = f1 + f2;
			f1 = f2;
			f2 = f3;
		}
	}
	printf("%d\n",f3);
	return 0;
}
 /* Dev-C++运行结果 
请输入您需要求的想的序列:6
8
*/ 

do…while

格式

​ do

​ {
​ …
​ }while(表达式);

do…while并不等价于for,当然也不等价于 while。主要是用于人机交互。

一元二次方程

/*
2023年5月29日18:39:46 
功能:一元二次方程 
*/

# include <stdio.h>
# include <math.h>
int main(void)
{
	double a,b,c;
	double delta;
	double x1,x2;
	char ch;
	do
	{
		printf("请输入一元二次方程的三个系数:\n");
		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 = %lf, x2 = %lf\n",x1,x2);
		}
		else if (0 == delta)
		{
			x1 = x2 = (-b) / (2*a);
			printf("有唯一解,x1 = x2 = %lf\n",x1,x2);
		}
		else
		{
			printf("无实数解!\n");
		} 
		printf("您想继续吗(Y/N):");
		scanf(" %c", &ch);//%c前面必须得加一个空格 
	}while ('y'==ch || 'Y'==ch);
	return 0;
}
 /* Dev-C++运行结果 
请输入一元二次方程的三个系数:
a = 1
b = 2
c = 3
无实数解!
您想继续吗(Y/N):y
请输入一元二次方程的三个系数:
a = 2
b = 3
c = 8
无实数解!
您想继续吗(Y/N):n
*/ 

break(重点)

  • 如果用于循环是用来终止循环,并且是距离它最近的一个循环。

  • break如果用于switch,则是用来终止switch。

  • break不能直接用于if,除非if属于循环内部的一个子句。

例子:

for (i=0; i<3; i++)
	{
		if (3>2)
			break; //break虽然是if内部的语句
		printf("AAA"); //但break终止的确实外部的for循环 
	}                  //永远不会输出

在多层循环中,break只能终止距离它最近的那个循环

	for (i=0; i<3; i++)
	{
		for (j=1; j<4; ++j)
			break;  //break只能终止距离它最近 
		printf("BBB"); 
	}

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

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

continue

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

例子:如果执行continue,则执行完该语句后,会执行语句3,跳过 C 和 D

    for (1; 2; 3)
    {
        A;
        B;
        continue;  // 如果执行该语句,则执行完该语句后,会执行语句3,跳过 C 和 D
        C;
        D;
    }

如果执行continue,则执行完该语句后,会执行表达式,跳过 C 和 D

    while (表达式)
    {
        A;
        B;
        continue;  // 如果执行该语句,则执行完该语句后,会执行表达式,跳过 C 和 D
        C;
        D;
    }

数组

为什么需要数组

  • 为了解决大量同类型数据的存储和使用问题
  • 为了模拟现实世界

数组的分类

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

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

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

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

例子:int a[5]

  • a是数组名
  • 5是数组元素的个数,元素就是变量
  • a[0]、a[1]、a[2]、a[3]、a[4]
  • 0~4为数组的下标
有关一维数组的操作
  • 初始化

    • 完全初始化

      ​ int a[5] = {1,2,3,4,5};

    • 不完全初始化,未被初始化的元素自动为零

      ​ int a[5] = {1,2,3};

    • 不初始化,所有元素是垃圾值

​ 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; //erroe 因为没有a[5]这个元素,最大只有a[4]

​ 如果要把a数组中的值全部复制给b数组

错误写法:

​ b = a; // error ,数组的名字代表第一个元素的地址

​ 正确写法:

​ for (i=0; i<5; ++i)

​ b[i] = a[i];

把一个数组的元素倒置

/*
2023年5月29日23:36:19
功能:把一个数组的元素倒置 
*/

# include <stdio.h>

int main(void)
{
	int a[7] = {1,2,3,4,5,6,7};
	int i,j,t;
	i=0;
	j=6;
	while (i<j)
	{
		t = a[i];
		a[i] = a[j];
		a[j] = t;
		i++;
		j--;
	}
	for (i=0; i<7; i++)
		printf("%d\n",a[i]);
	return 0;
}
 /* Dev-C++运行结果 
7
6
5
4
3
2
1
*/ 

二维数组

int a[3] [4]

总共是12个元素,可以当做3行4列看待,这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}

​ }‘’

输出二维数组内容

/*
2023年5月30日00:22:39
功能:输出二维数组 
*/

# include <stdio.h>

int main(void)
{
	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("%-5d",a[i][j]); // %-5d 表示左对齐,每个数占用5个字节
		printf("\n");	
	} 
	return 0;
}
 /* Dev-C++运行结果 
1    2    3    4
5    6    7    8
9    10   11   12
*/ 
	

多维数组

实际上是不存在多维数组的,因为在计算机硬件中,内存是线性一维的。

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

比如:
  int a[3] [4];
该数组是含有3个元素的一维数组,只不过每个元素可以再分成 4 个小元素。
  int a[3] [4] [5];
该数组是含有3个元素的一维数组,只不过每个元素都是 4 行 5 列的二维数组

函数

为什么需要函数

  • 避免了重复性操作

  • 有利于程序的模块化(面向过程,每一个功能可以用函数实现)

例子:

/*
2023年5月30日11:43:58
函数的第一个例子 
*/

# include <stdio.h>
//max是函数的名字,i和j是形式参数,简称形参 void表示函数没有返回值 
void max(int i, int j) //4.进入max函数 把a赋给i,b赋给j
{   			//内存给变量i,j开辟空间,max函数每次执行完后,会重新开辟空间
	if (i > j)
		printf("%d\n",i);
	else
		printf("%d\n",j);
}
int main(void)  //程序从main函数进入以及退出
{   
	int a,b,c,d,e,f; // 1.首先执行
	a=1,b=2,c=3,d=9,e=-5,f=100; // 2.依次执行a=1,b=2...
	max(a, b); //3.调用max函数
	max(c, d); //5.调用max函数
	max(e, f);
/*	if (a > b)
		printf("%d\n",a);
	else
		printf("%d\n",b);
	if (c > d)
		printf("%d\n",c);
	else
		printf("%d\n",d);
	if (e > f)
		printf("%d\n",e);
	else
		printf("%d\n",f);
*/
	return 0;
}
 /* Dev-C++运行结果 
2
9
100
*/ 

什么叫函数(重点)

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

  • 物理上:能够接收数据[当然也可以不接收]

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

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

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

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

/*
2023年5月30日12:15:29
函数举例2 
*/

# include <stdio.h>

int f(void)  //括号中的void表示该函数不能接收数据,int表示函数返回值是int类型 
{
	return 10;
}
void g(void) //函数名前面的void表示该函数没有返回值 
{
//	return 10;  //错误,与12行行首的void相矛盾 
}
int main(void)
{	
	int j = 88;
	j = f();
	printf("%d\n",j); 
//	j = g(); //错误,因为g函数没有返回值 
	return 0;
}
 /* Dev-C++运行结果 
10
*/ 

如何定义函数

定义:

函数的返回值 函数的名字(函数的形参列表)

{

​ 函数的执行体

}

  1. 函数定义的本质是详细描述函数之所以能够某个特定功能

  2. return 表达式;的含义:

    (1)终止被调函数,向主调函数返回表达式的值

    (2)如果表达式为空,则只终止函数,不向主调函数返回任何值

    (3)break是用来终止循环和switch的,return是用来终止函数的

    例子:

    /*
    2023年5月30日17:39:52 
    return和break 
    */
    
    # include <stdio.h>
    
    void f(void)
    {
    	int i;
    	for (i=0; i<5; i++)
    	{
    		printf("你好!\n");
    		return;  //return只用来终止整个函数,不向主调函数返回任何值
    	}
    	printf("好好!\n");
    }
    
    int main(void)
    {
    	f();
    	return 0; //第一:终止函数,第二:向主调函数返回0
    }
     /* Dev-C++运行结果 
    你好!
    */ 
    
  3. 函数返回值的类型也称为函数的类型,因为如果 函数名前的返回值类型和 函数执行体中的return表达式;中表达式的类型不同的话,则最终函数返回值的类型 以函数名前的返回值类型为准

例子:

/*
2023年5月30日17:39:52 
到底什么是函数的类型 
*/

# include <stdio.h>

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

int main(void)
{
	double x = 6.6;
	
	x = f();
	printf("%lf\n",x);
}
 /* Dev-C++运行结果 
10.000000
*/ 

函数的分类

  • 有参函数和无参函数
  • 有返回值函数和无返回值函数
  • 库函数和用户自定义函数
  • 值传递函数和地址传递函数
  • 普通函数和主函数(main函数)
    • 一个程序必须有且只能有一个主函数
    • 主函数可以调用普通函数,普通函数不能调用主函数
    • 普通函数可以相互调用
    • 主函数是程序的入口,也是程序的出口

函数举例

例:1:

/*
2023年5月30日19:36:03
*/

# include <stdio.h>
 
void max1(int i, int j) //第一,找最大值;第二,对最大值处理 
{
	if (i > j)
		printf("%d\n",i);
	else
		printf("%d\n",j);
}

int max2(int i, int j) //只用一个功能,找最大值 
{
	if (i > j)
		return i;
	else
		return j;
}

int main(void)
{
	int a,b,c,d,e,f;
	a = 1, b = 2, c= 3, d = 9, e = -5, f = 100;  
	printf("%d\n",max2(a,b));
	printf("%d\n",max2(c,d));
	printf("%d\n",max2(e,f));
/*	
	max1(a, b); 
	max1(c, d); 
	max1(e, f); */
	return 0;
}
 /* Dev-C++运行结果 
2
9
100
*/ 

例2:判断一个数字是不是素数

/*
2023年5月30日20:17:37
功能:判断一个数字是不是素数 
思想:素数只能被1和自己本身整除
	val被2~val-1整除 说明这个数不是素数 
*/

# include <stdio.h>

bool IsPrime(int val)
{
	int i;  
	for (i=2; i<val; i++){
		if (val%i == 0)
			break;
	}
	if (val == i)  // i没有执行过if语句 
		return true;
	else
		return false;
}

int main(void)
{
	int m;
	scanf("%d", &m);
	if ( IsPrime(m) )
		printf("Yes!\n");
	else 
		printf("No!\n");
	
	return 0;
} 
 /* Dev-C++运行结果 
121
No!
-----
11
Yes!
*/ 

注意的问题

函数调用和函数定义的顺序
  • 如果函数调用写在了函数定义前面,则必须加函数前置声明

  • 函数前置声明:

    1. 告诉编译器即将可能出现的若干个字母代表的是一个函数

    2. 告诉编译器即将可能出现的若干个字母所代表的函数的形参和返回值的具体情况

    3. 函数声明是一个语句,末尾必须加分号

    4. 对库函数的声明是通过 # include <库函数所在的文件的名字.h> 来实现的

/* 时间:2023年5月30日23:00:03 
函数声明_1 
*/
# include <stdio.h>

 // void f(int); 错误程序从上向下读,有接收的数据,但
 //在main函数中调用f()没有传入的值 
void f(); // 函数声明,分号不能丢掉 

int main(void)
{
	f();
	return 0;
}
void f(void)
{
	printf("哈哈!\n");
}
/* 时间:2023年5月30日23:00:03 
   一定要明白改程序为什么是错误的
   一定要明白改程序第7行生效之后为什么就正确了 
*/
# include <stdio.h>

//void f(void)  // 前置声明,7行 
void g(void)
{
	f(); //因为函数f的定义放在了调用f语句的后面,所以语法出错 
}
void f(void)
{
	printf("哈哈!\n");
}
int main(void)
{
	g();
	return 0;
}
形参和实参
  • 个数相同
  • 位置一一对应
  • 数据类型必须相互兼容
void f(int i, float x) //形参 
{
	printf("%d\n",i);
}
int main(void)
{
	f(5, 6.6); //实参 
	return 0;
}

如何在开发中合理设计函数

  • 一个函数的功能尽量独立,单一
  • 多学习,多模仿

1、判断一个数是不是素数

/*
时间:2023年5月31日11:24:09 
*/

# include <stdio.h>

bool IsPrime(int val)
{
	int i;
	for (i=2; i<val; i++)
	{
		if (val%i == 0)
			break;
	}
	if ( val == i)
		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;
}
 /* Dev-C++运行结果 
5
Yes! 
-----
121
No!
*/ 

2、

/*
时间:2023年5月31日11:24:09 
求1到某个数字之间(包括该数字)所有的素数,并将其输出
只用main函数实现,有局限性:
	1. 代码的重用性不高
	2. 代码不容易理解 
*/
# include <stdio.h>

int main(void)
{
	int val;
	int i,j;
	scanf("%d",&val);
	for (i=2; i<=val; i++) //从2到这个数 
	{ 
		//判断这个数是否是素数,是输出,不是不输出 
		for (j=2; j<i; j++)
		{
			if (0 == i%j)
				break;
		}
		if (j == i)
			printf("%-3d",i);
	}
	return 0;
}
 /* Dev-C++运行结果 
50
2  3  5  7  11 13 17 19 23 29 31 37 41 43 47
*/ 

3、

/*
时间:2023年5月31日12:34:43
求1到某个数字之间(包括该数字)所有的素数,并将其输出
用1个函数来判断一个数字是否是素数 
	优点:
		代码比 例2 更容易理解
		代码的可重用性比 例2 高
	缺点:
		可重用性仍然不是非常高
		比如求1000个数字,求他们每个数字从1到他本身的素数
		则
			for (i=2; i<=val; i++) 
			{ 
				if (IsPrime(i))
					printf("%-3d",i);
			} 
		要写1000次 
*/

# include <stdio.h>

bool IsPrime(int m)
{
	int i;
	for (i=2; i<m; ++i)
	{
		if (0 == m%i)
			break;
	}
	if (m == i)
		return true;
	else
		return false;
}
int main(void)
{
	int val;
	int i;
	scanf("%d",&val);
	for (i=2; i<=val; i++) //从2到这个数 
	{ 
		if (IsPrime(i))
			printf("%-3d",i);
	}
	return 0;
}
 /* Dev-C++运行结果 
50
2  3  5  7  11 13 17 19 23 29 31 37 41 43 47
*/ 

4、

/*
时间:2023年5月31日12:42:43
求1到某个数字之间(包括该数字)所有的素数,并将其输出
	本程序 和 例3 相比较
 	代码量更少,可重用性更高 
*/

# include <stdio.h>

//本函数的功能是:判断m是否是素数,是返回true,不是返回false 
bool IsPrime(int m)
{
	int i;
	for (i=2; i<m; ++i)
	{
		if (0 == m%i)
			break;
	}
	if (m == i)
		return true;
	else
		return false;
}
//本函数的功能是把1到n之间的所有素数在显示器上输出 
void TraverseVal(int n)
{
	int i;
	for (i=2; i<=n; i++) //从2到这个数 
	{ 
		if (IsPrime(i))
			printf("%-3d",i);
	}
} 

int main(void)
{
	int val;
	scanf("%d",&val);
	TraverseVal(val); 
	return 0;
}
 /* Dev-C++运行结果 
50
2  3  5  7  11 13 17 19 23 29 31 37 41 43 47
*/ 

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

常用的系统函数

  • double sqrt(double x)

    求x的平方根

  • int abs(int x)

    求x的绝对值

  • double fabs(double x)

    求x的绝对值

变量的作用域和存储方式

  • 按作用域分

    • 全局变量

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

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

    • 局部变量

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

      ​ void f(int i)

      ​ {

      ​ int j =20

      ​ }

      ​ i和j都属于局部变量

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

  • 注意的问题:

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

    • 在一个函数内部如果定义的局部变量的名字和全局变量名字一样时,局部变量会屏蔽掉全局变量
/*
时间:2023年5月31日17:00:08
局部变量把全局变量屏蔽 
*/

# include <stdio.h>

int i = 99; //全局变量 
void f(int i) //局部变量 
{
	printf("i = %d\n",i);
}
int main(void)
{
	f(8);
	return 0;
}
 /* Dev-C++运行结果 
i = 8
*/ 

指针

指针和指针变量
  • 指针就是地址,地址就是指针
  • 地址就是内存单元的编号(1000H)
  • 指针变量是存放地址的变量
  • 指针和指针变量是两个不同的概念
  • 但是注意,通常我们叙述时会把指针变量简称为指针,实际它们含义并不一样

指针的重要性

  • 表示一些复杂的数据结构

  • 快速的传递数据,减少了内存的耗用(重点)

  • 使函数返回一个以上的值(重点)

  • 能直接访问硬件

  • 能够方便的处理字符串

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

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

指针的定义

地址
  • 内存单元的编号
  • 从零开始的非负整数
指针
  • 指针就是地址,地址就是指针
  • 指针变量就是存放内存单元编号的变量,或者说指针变量就是存放地址
  • 指针和指针变量是两个不同的概念
  • 但是注意,通常我们叙述时会把指针变量简称为指针,实际它们含义并不一样
  • 指针的本质就是一个操作受限的非负整数(可以减,不能加乘除)

指针的分类

基本类型指针(重点)
# include <stdio.h>

int main(void)
{
	int * p;   //p是变量的名字,int * 表示p变量存放的是int类型变量的地址
			   //int * p,不表示定义了一个名字叫做*p的变量
			   //int * p,应该这样理解:p是变量名,p变量的数据类型是int *类型
			   // 		  所谓int *类型 实际就是存放int变量的地址的类型 
	int i = 3;
	int j; 
	p = &i;
		/*
			1.p保存了i的地址,因此p指向i
			2.p不是i,i也不是p,更准确的说:修改p的值不影响i的值,修改i的值也不影响p的值
			3.如果一个指针变量指向了某个普通变量,则
					*指针变量  就完全等同于  普通变量
				例子:
					如果p是个指针变量,并且p存放了普通变量i的地址
					则p指向了普通变量i
					*p  就完全等同于  i 
					或者说:在所有出现*p的地方都可以替换成i
							在所有出现i的地方都可以替换成*p 
					*p 就是以p的内容为地址的变量 
		*/
	j = *p;  //等价于 j = i 
	printf("i=%d,j=%d",i,j); 
} 
/* Dev-C++运行结果 
i=3,j=3
*/

对于:int * p;

  1. p是变量的名字int * 表示p变量存放的是int类型变量的地址
  2. int * p,不表示定义了一个名字叫做*p的变量
  3. int * p,应该这样理解:p变量名p变量的数据类型是int *类型
  4. 所谓int *类型 实际就是存放int变量的地址的类型

对于:`p = &i;

  1. p保存了i的地址,因此p指向i
  2. p不是ii也不是p,更准确的说:修改p的值不影响i的值,修改i的值也不影响p的值
  3. 如果一个指针变量指向了某个普通变量,则 *指针变量 就完全等同于 普通变量
    例子:
    如果p是个指针变量,并且p存放了普通变量i地址,则p指向了普通变量i*p 就完全等同于 i,或者说:在所有出现*p的地方都可以替换成i;在所有出现i的地方都可以替换成*p
  4. *p 就是以p内容为地址的变量

image-20230531183903635

指针常见错误

例1:

/*
2023年5月31日23:03:29
指针常见错误_1 
*/
# include <stdio.h>

int main(void)
{
	int * p;
	int i = 5;
	*p = i;  // *p是以p内容为地址的变量 是int类型变量; i也是int类型 不会报错 
    // 但是p里面存的内容为垃圾值,
	printf("%d\n",*p);
	
	return 0;
}

例2:

/*
2023年5月31日23:17:19
指针常见错误_2 
*/
# include <stdio.h>

int main(void)
{
	int i = 5;
	int * p;
	int * q;
	
	p = &i;
//	*q = p;  //error语法编译会出错 *q是int类型变量,p是int * 变量 不一致 
	*q = *p; //q里面没有赋值 
	p = q; //q是垃圾值,q赋给p,p也变成垃圾值 
	printf("%d\n",*q);  /* error q内部空间是属于本程序的,所以本程序可以读写q的内容 
						[但是如果q内部是垃圾值,则本程序不能读写*q的内容,
						因为此时*q所代表的内存单元的控制权限并没有分配给本程序,
						所以本程序运行到17行是就会立即出错] 
						*q代表不知道的单元(q里垃圾值为地址的单元),不能读里面的值  */
	return 0;
}

经典指针程序_互换两个数字

/*
2023年5月31日23:03:29
经典指针程序_互换两个数字 
*/

#include <stdio.h>

void huhuan_1(int, int);
void huhuan_2(int *, int *)
void huhuan_3(int *, int *) 


int main(void)
{
	int a = 3;
	int b = 5;
	
	huhuan_3(&a, &b); 
	printf("a = %d, b = %d\n",a,b);
//	huhuan_2(&a, &b);//huhuan_2(*p, *q)是错误的,局部变量不能再另外一个函数中使用;huhuan_2(a,b)也是错误的 
//	huhuan_1(a, b);  a,b的值穿个上述函数后,内存开辟一片空间,在互换函数完成了,该内存会销毁
//	printf("a = %d, b = %d\n",a,b);  这里面a,b依旧没main函数里面a,b 
	
	return 0;
}

// 不能完成互换功能 
void huhuan_1(int a, int b) //执行到函数时,内存分配a,b,t空间,使用完毕后为函数分配的静态空间都会释放
{
	int t;
	t = a;
	a = b;
	b = t;
	return;
}
// 不能完成互换功能 
void huhuan_2(int * p, int * q) //形参名字是p和q,接收实参数据是p和q
{
	int * t; //如果要互换p和q的值,则必须是int *,不能是int,否则会出错 
	t = p;
	p = q;
	q = t;
} 
//可以完成互换功能 
void huhuan_3(int *p, int *q) //p指向a,q指向b 
{
	int t; //如果要互换*p和*q的值,则t必须定义成int,不能定义成int * 
	t = *p; //*p = a; *q = b;
	*p = *q; // p是int *,*p是int 
	*q = t;
}
/* Dev-C++运行结果 
a = 5, b = 3
*/

huhuan_2代表的图示,只是把p和q的内容进行了互换

image-20230601000709463

huhuan_3代表的图示,把p指向的a的值和q指向的b的值进行了互换

image-20230601005453131

image-20230601005506750

image-20230601005530793

附注:

* 的含义
  1. 乘法

  2. 定义指针变量

    int * p 定义了一个名字叫p的变量,int *表示p只能存放int变量的地址

  3. 指针运算符

    该运算符放在已经定义好的指针变量的前面

    如果p是一个已经定义好的指针变量

    *p表示:以p的内容为地址的变量

int * p;
*p = 5  //此时p为已经定义好的指针变量
如何通过被调函数修改主调函数普通变量的值
  1. 实参必须为该普通变量的地址

  2. 形参必须为指针变量

  3. 在被调函数中通过

    *形参名 = .....

    的方式就可以修改主调函数相关变量的值

/*
时间:2023年6月1日13:18:11 
*/
# include <stdio.h>

void g(int * p, int * q)
{
	*p = 1;  //p=&a,代表p指向a,*p=a 
	*q = 2;
}
int main(void)
{
	int a = 3, b = 4;
	g(&a, &b);
	printf("a = %d,b = %d\n",a,b);
	return 0;
} 
/* Dev-C++运行结果 
a = 1,b = 2
*/

指针和数组

指针和一维数组
一维数组名
  • 一维数组名是个指针常量,它存放的是一维数组第一个元素的地址
/*
时间:2023年6月1日13:39:02 
*/ 
# 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);  //#X以十六进制输出 
}
/* Dev-C++运行结果 
0X62FE00
0X62FE00
*/
下标和指针的关系

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

/*
时间:2023年6月1日17:59:11
*/ 
# include <stdio.h>

int main(void)
{
	int a[5] = {1,2,3,4,5};
	a[3] = *(3+a); //a指向a[0] *a=a[0];(3+a)指向a[3], a[3] = *(3+a)
	 
}
确定一个一维数组需要几个参数(如果一个函数要处理一个一维数组,则需要接收该数组的哪些信息)

需要两个参数:

  1. 数组第一个元素的地址(数组名)
  2. 数组的长度

举例1:确定一个一维数组需要几个参数_1

# include <stdio.h>
//f函数可以输出任何一个一维数组的内容 
void f(int * pArr, int len) //数组名以及数组长度 
{
	int i;
	for (i=0; i<len; ++i)
		printf("%d  ",pArr[i]); // *(pArr+i)
	printf("\n");
}

int main(void)
{
	int a[5] = {1,2,3,4,5};
	int b[6] = {-1,-2,-3,4,5,-6};
	int c[100] = {1,99,22,23};
	f(a,5); // a是int *类型
	f(b,6);
	f(c,100); 
	return 0;
}

举例2:确定一个一维数组需要几个参数_2

/*
时间:2023年6月1日19:18:53
一定要明白 8行的pArr[3]和 15行a[3]是同一个变量 
15行a和7行pArr的值一样,类型一样,但是是不一样的变量 
*/ 
# include <stdio.h>
void f(int * pArr, int len) //pArr存储a[0]的地址 
{
	pArr[3] = 88; //pArr[3]=*(pArr+3)  (pArr+3)指向第4个元素  *(pArr+3)代表第4个元素本身内容 
}
int main(void)
{
	int a[6] = {1,2,3,4,5,6};
	printf("%d\n",a[3]);
	f(a,6); // a代表a[0]的地址  
	printf("%d\n",a[3]); //因为a[3]=*(a+3)  (a+3)是第四个元素地址 ,*(a+3)代表第4个元素,所以a[3]代表第4个元素 
	return 0;
}
/* Dev-C++运行结果 
4
88
*/

图示:

image-20230601190539076

举例3:确定一个一维数组需要几个参数_3

# 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)
	pArr[i];
	printf("\n") ;
}
int main(void)
{
	int b[6] = {-1,-2,-3,4,5,-6};
	f(b, 6);
//	b[i] //与第8行等价 
	return 0;
} 
指针变量的运算

指针变量不能相加、不能相乘、也不能相除

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

# include <stdio.h>

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

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

预备知识:

sizeof(数据类型)

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

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

  • 假设p指向char类型变量(1个字节)

  • 假设q指向int类型变量(4个字节)

  • 假设r指向double类型变量(8个字节)

    p、q、r本身所占的字节数是否一样

    ​ 答案:p、q、r 本身所占的字节数是一样的

总结:

一个指针变量,无论它指向的变量占几个字节,该指针变量本身只占四个字节(每个单元需要64位,也就是8个字节)

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

/*
时间:2023年6月2日18:44:32
*/ 
# 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;
}
/* Dev-C++运行结果 
8 8 8
*/

image-20230602185322312

指针和二维数组

专题:(重点)

动态内存分配
传统数组的缺点:
  1. 数组长度必须事先指定,且只能是常整数,不能是变量

    例子:int a[5]; //ok int len = 5; int a[len]; //error

  2. 传统形式定义的数组,该数组的内存程序员无法手动释放,在一个函数运行期间,系统为该函数中数组分配的空间会一直存在,知道该函数运行完毕时,数组的空间才会被系统释放

  3. 数组的长度一旦定义,其长度就不能再更改

    数组的长度不能再函数运行的过程中动态的扩充或缩小

  4. A函数定义的数组,在A函数运行期间可以被其他函数使用,但A函数运行完毕之后,A函数中的数组将无法再被其他函数使用

    传统方式定义的数组不能跨函数使用

为什么需要动态分配内存

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

​ 传统数组也叫静态数组

malloc函数的使用

程序1:

/*
时间:2023年6月3日11:41:42
malloc 是 memory(内存) allocate(分配)的缩写 
*/

# include <stdio.h>
# include <malloc.h>  //不能省

int main(void)
{
	int i = 5; //分配了4个字节 静态分配
	int * p = (int *)malloc(4);
	/*
		1.要使用malloc函数,必须添加malloc.h这个头文件
		2.malloc函数只有一个形参,并且形参是整型
		3.4表示请求系统为本程序分配4个字节
		4.malloc函数只能返回第一个字节的地址(不能确定p指向的变量占几个字节,所以需要强制类型转换) 
		5.第9行分配了12个字节,p变量占8个字节,p所指向的内容占4个字节
		6.p本身所占的内容是静态分配,p所指向的内容是动态分配的 
	*/	
	* p = 5; //*p代表的就是一个int变量,只不过*p这个整型变量的内容分配方式和第8行的i变量的分配方式不同
	free(p); //free(p)表示把p所指向的内容给释放调,p本身的内容是静态的,不能由程序员手动释放 
}

程序2:

# include <stdio.h>
# include <malloc.h>
 
void f(int * q)
{
//	*p = 200; // p不属于这个函数
//	q = 200;  //q是 int *类型只能存放地址
//	**q = 200; //*q代表q指向的变量(是普通变量)普通变量不能加*
	*q = 200; 
//	free(q);  //把q所指向的内存释放掉 
}
int main(void)
{
	int * p = (int *)malloc(sizeof(int)); //sizeof(int)返回值是int所占的字节数 
	*p = 10;
	printf("%d\n",*p); // 10
	f(p); //p是int *类型 
	printf("%d\n",*p);// 200
}

image-20230603124846780

动态内存分配举例_动态数组的构造

int * pArr = (int *)malloc(len); //类似于 int pArr[len]

假设len=5,上述代码表示系统分配20个字节的内存空间,pArr存放的是第一个字节的地址,又由于pArrint *类型,所以pArr指向的是4个字节

int a[5]里面的a指向前4个字节,pArr指向前4个字节;a+1font>指向后4字节,pArr+1font>也指向后4个字节;a[0]font>代表第一个元素,pArr[0]也代表第二个元素。

例:动态数组的构造

/*
时间:2023年6月3日19:38:09 
*/
# include <stdio.h>
# include <malloc.h>

int main(void)
{
	int a[5]; //如果int占4个字节,则本书总共包含有20个字节,每4个字节被当做了一个int变量来使用 
	int * pArr;
	int len,i;
	//动态的构造一维数组 
	printf("请输入你要存放的元素个数:");
	scanf("%d",&len);
	pArr = (int *)malloc(4 * len); //第12行 本行动态的构造了一个一维数组,该一维数组的长度是len,该数组的每个元素是int类型,类似于int pArr[len] 
	//对一维数组进行操作
	for (i=0; i<len; i++)
		scanf("%d",&pArr[i]);
	//对一维数组进行输出
	printf("一维数组的内容是:\n");
	for (i=0; i<len; i++)
		printf("%d\n",pArr[i]);
	free(pArr); // 释放掉动态分配的数组
	
	return 0; 
}
/* Dev-C++运行结果 
请输入你要存放的元素个数:5
1 2 3 4 5
一维数组的内容是:
1
2
3
4
5
*/

image-20230603192507190

静态内存和动态内存的比较

多级指针

程序1:

# include <stdio.h>

int main(void)
{
	int i = 10;
	int * p = &i;
	int ** q = &p;
	int *** r = &q;
	// r = &p; // error 因为r是 int ***类型,r只能存放 int **类型变量的地址 
	printf("i = %d\n",***r);
	return 10;
} 
/* Dev-C++运行结果 
i = 10
*/

程序2:

# include <stdio.h>

void f(int ** q)
{
	// *q就是p 
} 
void g()
{
	int i = 10;
	int * p = &i;
	f(&p); //p是int *类型,&p是int **类型 
} 
int main(void)
{
	g();
	return 0;	
} 

***r = i ; // *r = q **r = p ***r = i

image-20230603221814993

跨函数使用内存的问题

静态内存不可以跨函数使用。或者说是:静态内存在函数执行期间可以被其他函数使用,但是在函数执行完毕后就不能再被其他函数使用了。

动态内存可以跨函数使用。动态内存在函数执行完毕之后仍然可以被其他函数使用。

程序1:

# include <stdio.h>

void f(int ** q) //q是个指针变量,无论q是什么类型,都只占4个字节 
{
	int i = 5;
	//*q等价于p  q和**q不都等价与p 
//	*q = i; // error 因为*q = i; 等价于*p = q -> p = i; 
	*q = &i; // p = &i; 
}

int main(void)
{
	int *p;
	f(&p);
	printf("%d\n",*p); //本语句语法没有问题,但逻辑上有问题;f()使用完后,系统会释放内存(p指向i变量)i变量的内存已经不存在 
	return 0;
}

程序2:

/*
时间:2023年6月4日12:08:44 
*/
# include <stdio.h>
# include <malloc.h>

void f(int **q)
{
	* q = (int *)malloc(sizeof(int));
		//等价于 p =  (int *)malloc(sizeof(int));
	//q = 5; error
	//*q = 5; //p = 5;error
	**q = 5; //*p = 5
}
int main(void)
{
	int * p;
	f(&p);
	printf("%d\n",*p);
	return 0;
}

结构体

为什么需要结构体

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

什么叫结构体

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

如何定义结构体

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

//第一种 这只是定义了一个新的数据类型,并没有定义变量
struct Student{
   int age;
	float score;
	char sex;
};
//第二种方式
struct Student2{
	int age;
	float score;
	char sex;
} st2; 
//第三种方式
struct{
	int age;
	float score;
	char sex;
} st3; 
怎样使用结构体变量
赋值和初始化
  • 定义的同时可以整体赋初值
  • 如果定义完之后,则只能单个的赋初值
/*
时间:2023年6月4日13:07:56
*/
# include <stdio.h>
 
//第一种 
struct Student{
	int age;
	float score;
	char sex;
};

int main(void)
{
	struct Student st1 = {80, 66.6, 'F'}; //初始化,定义的同时赋初值
	struct Student st2;
	st2.age = 10;
	st2.score = 88.5;
	st2.sex = 'F';
	printf("%d %f %c\n",st1.age,st1.score,st1.sex);
	printf("%d %f %c\n",st2.age,st2.score,st2.sex); 
}
/* Dev-C++运行结果 
80 66.599998 F
10 88.500000 F
*/

如何取出结构体变量中的每一个成员(重点)
  1. 结构体变量名 **. **成员名

  2. 指针变量名 -> 成员名(更常用)

    指针变量名->成员名在计算机内部会被转化成(*指针变量名)**.**成员名的方式来执行;所以说这两种方式是等价的

    例子:

    /*
    时间:2023年6月4日16:53:25
    */
    # include <stdio.h>
     
    //第一种 
    struct Student{
    	int age;
    	float score;
    	char sex;
    };
    
    int main(void)
    {
    	struct Student st = {80, 66.6F, 'F'}; //初始化,定义的同时赋初值
    	struct Student * pst = &st; //&st不能改成st
    	st.age = 20; //第一种方式 
    	pst->score = 88.8f; // 第二种方式  88.8在C语言中默认是double类型,如果希望一个实数是float类型,则必须在末尾加f或F,因此66.6f是float类型 
    	printf("%d %f\n",pst->age,st.score);
    	return 0;
    }
    /* Dev-C++运行结果 
    20 88.800003  float丢失精度,存近似值 
    */
    
  3. pst->age 在计算机内部会被转化成(*pst).age,这就是**->**的含义,也是一种硬性规定

  4. 所以pst->age 等价于(*pst).age也等价于st.age

  5. 我们之所以知道pst->age等价于st.age,是因为pst->age是被转化成了(*pst).age来执行

  6. pst->age的含义:

    pst->age所指向的那个结构体变量中的age这个成员

结构体变量和结构体指针变量作为函数参数传递的问题
  • 推荐使用结构体指针变量作为函数参数来传递

    例子:

    /*
    时间:2023年6月4日2023年6月4日19:24:00
    通过函数完成对结构体变量的输入和输出
    发送地址还是发送内容 :传内容需要两片相同大小的内存,传地址只需要指针变量的内存4个字节 
    目的:指针的优点之一:
    			快速的传递数据
    		耗用内存小
    		执行速度快 
    */
    # 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;
    	InputStudent(&st);
    	OutputStudent(&st);
    //	printf("%d %c %s\n",st.age,st.sex,st.name);
    } 
    void InputStudent(struct Student * pst) //对结构体变量输入,必须发送st的地址,因为要改变变量的值 
    {
    	pst->age = 20;
    	strcpy((*pst).name,"张三");
    	pst->sex = 'F';
    }
    void OutputStudent(struct Student *ps) //对结构体变量输出,可以发送st的地址也可以直接发送st的内容,因为不改变st的值,只是拷贝了st的值进行输出 
    {									// 但为了减少内存的耗费,也为了提高执行速度,推荐发送地址 
    	printf("%d %c %s\n",ps->age,ps->sex,ps->name);
    }
    /* Dev-C++运行结果 
    20 F 张三
    */
    
结构体变量的运算
  • 结构体变量不能相加,不能相减,也不能相互乘除

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

    例子:

    struct Student
    { 
    	int age;
    	char sex;
    	char name[100];
    };//分号不能省
    struct Student st1,st2;
    st1+st2  st1*st2  st1/st2 都是错误的
    st1 = st2    或者  st2  =  st1 都是正确的
    
冒泡排序
/*
时间:2023年6月4日22:09:09    10,2,8,-8,11,0   
1.i=0 i<5;        j=0;	j<6-1-0=5;  	a[0]=10 > a[1]=2    交换 a[1]=10 a[0]=2
第一趟	   ++j    j=1;	j<6-1-0=5; 	    a[1]=10 > a[2]=8    交换 a[2]=10 a[1]=8 
 	       ++j    j=2	j<6-1-0=5;  	a[2]=10 > a[3]=-8   交换 a[2]=-8 a[3]=10
		   ++j 	  j=3	j<6-1-0=5;      a[3]=10 <  a[4]=11  不交换      2 8 -8 10 0 11
		   ++j 	  j=4	j<6-1-0=5;    	a[4]=11 > a[5]=0 	交换 a[4]=0 a[5]=11 
2.i=1  ...
*/
# include <stdio.h>

void sort(int * a, int len)
{
	int i,j,t;
	for (i=0; i<len-1; ++i)   //趟数 0~4 确定5个数
	{
		for (j = 0; j<len-1-i; ++j)   //每一趟确定一个最大的数(这个数就不用比较了)-1(第一个本身自己不用比较) 
		{
			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;
 } 
 /* Dev-C++运行结果 
 -8  0  2  8  10  11
*/
举例
动态构造存放学生信息的结构体数组

动态构造一个数组,存放学生的信息,然后按分数排序输出

/*
时间:2023年6月5日13:24:43
学生管理系统 
*/ 
# include <stdio.h>
# include <malloc.h>

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

int main(void)
{
	int len,i,j;
	struct Student * pArr;
	struct Student t;
	//构造动态一维数组 
	printf("请输入学生个数:\n");
	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("score = ");
		scanf("%f",&pArr[i].score);
		printf("name  = ");
		scanf("%s",pArr[i].name);
	} 
	printf("--------------------\n") ;
	printf("--------------------\n");
	//冒泡排序 按学生成绩升序排序 
	for (i=0; i<len; i++)
	{
		for (j=0; j<len-1-i; j++)
			if (pArr[j].score > pArr[j+1].score)  //pArr[j] > pArr[j+1]是错误的,因为数组里面结构体类型有三个内容,不能比较 
			{
				t = pArr[j];  //后面不用加.score,因为排序是整体换,不是只换score 
				pArr[j] = pArr[j+1];
				pArr[j+1] = t;	
			}	
	}	
	//输出
	for (i=0; i<len; i++)
	{
		printf("第%d个学生信息是:\n",i+1);
		printf("age   = %d\n", pArr[i].age);
		printf("score = %f\n", pArr[i].score);
		printf("name  = %s\n", pArr[i].name);
	} 
	return 0;
}
/* Dev-C++运行结果 
请输入学生个数:
3
请输入第1个学生信息:
age   = 20
score = 56.5
name  = 张三
请输入第2个学生信息:
age   = 18
score = 66.5
name  = 李四
请输入第3个学生信息:
age   = 25
score = 47.5
name  = 李强
--------------------
--------------------
第1个学生信息是:
age   = 25
score = 47.500000
name  = 李强
第2个学生信息是:
age   = 20
score = 56.500000
name  = 张三
第3个学生信息是:
age   = 18
score = 66.500000
name  = 李四
*/
/*
时间:2023年6月5日13:37:46
学生管理系统(函数)
*/ 
# include <stdio.h>
# include <malloc.h>

struct Student{
	int age;
	float score;
	char name[100];
};
	//输入
void Input(struct Student * pArr,int len)
{
	int i;
	for (i=0; i<len; i++)
	{
		printf("请输入第%d个学生信息:\n",i+1);
		printf("age   = ");
		scanf("%d",&pArr[i].age);
		printf("score = ");
		scanf("%f",&pArr[i].score);
		printf("name  = ");
		scanf("%s",pArr[i].name);
	} 
} 
	//冒泡排序 按学生成绩升序排序 
void sort(struct Student * pArr,int len)
{
	int i,j;
	struct Student t;
	for (i=0; i<len; i++)
	{
		for (j=0; j<len-1-i; j++)
			if (pArr[j].score > pArr[j+1].score)  //pArr[j] > pArr[j+1]是错误的,因为数组里面结构体类型有三个内容,不能比较 
			{
				t = pArr[j];  //后面不用加.score,因为排序是整体换,不是只换score 
				pArr[j] = pArr[j+1];
				pArr[j+1] = t;	
			}	
	}	
}
//输出
void Output(struct Student * pArr,int len)
{
	int i;
	for (i=0; i<len; i++)
	{
		printf("第%d个学生信息是:\n",i+1);
		printf("age   = %d\n", pArr[i].age);
		printf("score = %f\n", pArr[i].score);
		printf("name  = %s\n", pArr[i].name);
	} 
}
int main(void)
{
	int len,i,j;
	struct Student * pArr;
	struct Student t;
	//构造动态一维数组 
	printf("请输入学生个数:\n");
	scanf("%d",&len);
	pArr = (struct Student *)malloc(len*sizeof(struct Student));
	Input(pArr,len);
	printf("--------------------\n") ;
	printf("--------------------\n");
	sort(pArr,len);
	Output(pArr,len);
	return 0;
}
/* Dev-C++运行结果 
请输入学生个数:
3
请输入第1个学生信息:
age   = 20
score = 56.5
name  = 张三
请输入第2个学生信息:
age   = 18
score = 66.5
name  = 李四
请输入第3个学生信息:
age   = 25
score = 47.5
name  = 李强
--------------------
--------------------
第1个学生信息是:
age   = 25
score = 47.500000
name  = 李强
第2个学生信息是:
age   = 20
score = 56.500000
name  = 张三
第3个学生信息是:
age   = 18
score = 66.500000
name  = 李四
*/

把输入、输出、冒泡排序用函数调用来写了一下,不知道是不是正确

/*
时间:2023年6月5日13:37:46
学生管理系统(函数)
*/ 
# include <stdio.h>
# include <malloc.h>

struct Student{
	int age;
	float score;
	char name[100];
};
	//输入
void Input(struct Student * pArr,int len)
{
	int i;
	for (i=0; i<len; i++)
	{
		printf("请输入第%d个学生信息:\n",i+1);
		printf("age   = ");
		scanf("%d",&pArr[i].age);
		printf("score = ");
		scanf("%f",&pArr[i].score);
		printf("name  = ");
		scanf("%s",pArr[i].name);
	} 
} 
	//冒泡排序 按学生成绩升序排序 
void sort(struct Student * pArr,int len)
{
	int i,j;
	struct Student t;
	for (i=0; i<len; i++)
	{
		for (j=0; j<len-1-i; j++)
			if (pArr[j].score > pArr[j+1].score)  //pArr[j] > pArr[j+1]是错误的,因为数组里面结构体类型有三个内容,不能比较 
			{
				t = pArr[j];  //后面不用加.score,因为排序是整体换,不是只换score 
				pArr[j] = pArr[j+1];
				pArr[j+1] = t;	
			}	
	}	
}
//输出
void Output(struct Student * pArr,int len)
{
	int i;
	for (i=0; i<len; i++)
	{
		printf("第%d个学生信息是:\n",i+1);
		printf("age   = %d\n", pArr[i].age);
		printf("score = %f\n", pArr[i].score);
		printf("name  = %s\n", pArr[i].name);
	} 
}
int main(void)
{
	int len,i,j;
	struct Student * pArr;
	struct Student t;
	//构造动态一维数组 
	printf("请输入学生个数:\n");
	scanf("%d",&len);
	pArr = (struct Student *)malloc(len*sizeof(struct Student));
	Input(pArr,len);
	printf("--------------------\n") ;
	printf("--------------------\n");
	sort(pArr,len);
	Output(pArr,len);
	return 0;
}
/* Dev-C++运行结果 
请输入学生个数:
3
请输入第1个学生信息:
age   = 20
score = 56.5
name  = 张三
请输入第2个学生信息:
age   = 18
score = 66.5
name  = 李四
请输入第3个学生信息:
age   = 25
score = 47.5
name  = 李强
--------------------
--------------------
第1个学生信息是:
age   = 25
score = 47.500000
name  = 李强
第2个学生信息是:
age   = 20
score = 56.500000
name  = 张三
第3个学生信息是:
age   = 18
score = 66.500000
name  = 李四
*/

枚举

什么是枚举

  • 把一个事物所有可能得取值——列举出来

怎样使用枚举

/*
时间:2023年6月5日14:06:18 
枚举 
*/ 
# include <stdio.h>

enum WeekDay
{
	MonDay, TuesDay, WednesDay, ThursDay, FriDay, SaturDay, SunDay 
}; // 0		1			2 			3		4		5			6
int main(void)
{
	enum WeekDay day = WednesDay;
	printf("%d\n",day);
	return 0;
}
 /* Dev-C++运行结果 
 2
*/

枚举的优缺点

  • 代码更安全
  • 书写麻烦

专题

原码

原码也叫符号 - 绝对值码。最高位表示0表示正1表示负,其余二进制位是该数字的绝对值的二进制位。原码简单易懂,但是加减运算复杂,存在加减乘除四种运算,增加了CPU的复杂度,零的表示不唯一。

反码

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

移码

移码表示数值平移 n 位,n 称为移码量。移码主要用于浮点数的阶码的存储。

补码

已知十进制求二进制
  1) 求正整数的二进制
    除2取余,直至商为零,余数倒序排序。
  2) 求负整数的二进制
    先求与负数相对应的正整数的二进制代码,然后将所有位取反,末尾加1,不够位数时,左边补1(也可以从右往左起,第一个1以左都取反,其余不变)。

​ 例:-3 先求+3的二进制(011),取反(100),再加1就是(101),如果-3int类型,int占4个字节也就是32位,前面则要补29个1

3) 求零的二进制
    全是零。
已知二进制求十进制
  如果首位是0,则表明是正整数,按普通方法求。
  如果首位是1,则表明是负整数,将所有位取反末位加1,所得数字就是该负数的绝对值。(右往左起,第一个1以左都取反,其余不变,所得数字就是该负数的绝对值,然后添上符号,(101111)—> 所得绝对值为(010001)=17—>添上符号 **-**17
  如果全是0,则对应的十进制数字就是0.

8位补码的范围是:-128~+127

正整数最大(0111 1111)= 127 负整数最小(1000 0000)= -128

数组
  • 优点

    • 存取速度快
  • 缺点

    • 需要一个连续的很大的内存
    • 插入和删除元素的效率很低
链表
  • 优点

    • 插入删除元素效率高
    • 不需要一个连续的很大的内存
  • 缺点

    • 查找某个位置的元素效率低
  • 首节点:存放第一个有效数据的节点。

  • 尾节点:存放最后一个有效数据的节点。

  • 头节点:头结点的数据类型和首节点的类型是一模一样的。头结点是首节点前面的那个节点。头结点并不存放有效数据。设置头结点的目的是为了方便对链表的操作。

  • 头指针:存放头节点地址的指针变量。

  • 注意:确定一个链表需要一个参数,即头指针。

位运算符
运算符名称举例
&按位与1&1 = 1
1&0 = 0
0&1 = 0
0&0 = 0
5&7 = 5
5:0110
7:0111
按位与:0110=5
|按位或1|0 = 1
1|1 = 1
0|1 = 1
0|0 = 0
~按位取反~i就是把i变量所
有的二进制位取反
~3 = -4
^按位异或相同为零,不同为11^0 = 1
0^1 = 1
1^1 = 0
0^0 = 0
<<按位左移i<<1:表示把i的所有二
进制位左移一位,右边补0
左移n位相当于
乘以2的n次方
>>按位右移i>>3表示把i的所有而进制位
右移3位,左边一般是补0,(也可能补1)
右移n位相当于
除以2的n次方
(前提数据不能丢失)

面试题:

(1) i = i*8;

(2) i = i<<3;

请问上述两个语句,哪个语句执行的速度快

答案:(2)快,

位运算符的现实意义:通过位运算符我们可以对数据的操作精确到每一位。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值