黑马程序员C语言基础(第五天)运算符与表达式、程序流程结构、数组和字符串、函数

481 篇文章 79 订阅
170 篇文章 13 订阅

https://www.bilibili.com/video/BV15W411K7k6?p=93&spm_id_from=pageDriver

黑马程序员C语言基础(第五天)运算符与表达式、程序流程结构、数组和字符串、函数

文章目录

运算符与表达式

常用运算符分类

运算符类型	作用
算术运算符	用于处理四则运算
赋值运算符	用于将表达式的值赋给变量
比较运算符	用于表达式的比较,并返回一个真值或假值
逻辑运算符	用于根据表达式的值返回真值或假值
位运算符	用于处理数据的位运算
sizeof运算符	用于求字节数长度

算术运算符

运算符	术语	示例	结果
+	正号	+3	3
-	负号	-3	-3
+10 + 5	15
-10 - 5	5
*10 * 5	50
/10 / 5	2
%	取模(取余)	10 % 3	1
++	前自增	a=2; b=++a;	a=3; b=3;
++	后自增	a=2; b=a++;	a=3; b=2;
--	前自减	a=2; b=--a;	a=1; b=1;
--	后自减	a=2; b=a--;	a=1; b=2;
两个数相除,要想得到小数,分子分母必须有一个是小数,否则结果只会取整(可以把其中一个数乘以1.0)

示例:

#include <stdio.h>

int main()
{
	int a = 5;
	int b = 2;
	double c;
	double d;

	c= a/b;
	d = a*1.0/b;

	printf("%lf\n", c);//2.000000
	printf("%lf\n", d);//2.500000

	return 0;
}
前自增和后自增区别

b = a++ 是先把 a 的值赋给b,再自身+1

#include <stdio.h>

int main()
{
	int a;
	int b;

	
	
	//后置++
	a = 1;
	b = 0;
	b = a++;
	printf("%d %d\n", a, b);//2 1

	//前置++
	a = 1;
	b = 0;
	b = ++a;
	printf("%d %d\n", a ,b);//2 2
	
	//其他
	a = 1;
	b = 0;
	a++;//++a也一样的
	b = a;
	printf("%d %d\n", a ,b);//2 2

	return 0;
}

赋值运算符

运算符	术语	示例	结果
=	赋值	a=2; b=3;	a=2; b=3;
+=	加等于	a=0; a+=2;	a=2;
-=	减等于	a=5; a-=3;	a=2;
*=	乘等于	a=2; a*=2;	a=4;
/=	除等于	a=4; a/=2;	a=2;
%=	模等于	a=3; a%2;	a=1;

比较运算符

C 语言的比较运算中, “真”用数字“1”来表示, “假”用数字“0”来表示。

运算符	术语	示例	结果
==	相等于	4 == 3	0
!=	不等于	4 != 3	1
<	小于	4 < 3	0
>	大于	4 > 3	1
<=	小于等于	4 <= 3	0
>=	大于等于	4 >= 1	1

逻辑运算符

逻辑运算中,只要不为0都为真

运算符 术语 示例 结果
! 非 !a 如果a为假,则!a为真;
如果a为真,则!a为假。
&& 与 a && b 如果a和b都为真,则结果为真,否则为假。
|| 或 a || b 如果a和b有一个为真,则结果为真,二者都为假时,结果为假。

逻辑优化示例
//Dontla
#include <stdio.h>

int main()
{
	int c = 0;
	1 || (c = 250);
	printf("%d\n", c);//0
	
	c = 0;
	0 || (c = 250);
	printf("%d\n", c);//250

	return 0;
}

运算符优先级

注意特殊情况:
b = a++; 跟 b = (a++);等价,不会因为括号而影响结果
在这里插入图片描述
在这里插入图片描述

类型转换

数据有不同的类型,不同类型数据之间进行混合运算时必然涉及到类型的转换问题。

转换的方法有两种:

  • 自动转换(隐式转换):遵循一定的规则,由编译系统自动完成。
  • 强制类型转换:把表达式的运算结果强制转换成所需的数据类型。

类型转换的原则:占用内存字节数少(值域小)的类型,向占用内存字节数多(值域大)的类型转换,以保证精度不降低。
在这里插入图片描述

隐式类型转换

示例1:

#include <stdio.h>

int main()
{
	int num = 5;
	printf("s1=%d\n", num / 2);//s1=2
	printf("s2=%lf\n", num / 2.0);//s2=2.500000

	return 0;
}

示例2:

#include <stdio.h>

int main()
{
	double a;
	int b = 10;
	a = b;
	printf("%lf\n", a);//10.000000


	return 0;
}

强制类型转换

强制类型转换指的是使用强制类型转换运算符,将一个变量或表达式转化成所需的类型,其基本语法格式如下所示:

(类型说明符) (表达式)

示例1:

#include <stdio.h>

int main()
{
	float x = 0;
	int i = 0;
	x = 3.6f;

	i = x;			//x为实型, i为整型,直接赋值会有警告
	i = (int)x;		//使用强制类型转换

	printf("x=%f, i=%d\n", x, i);//x=3.600000, i=3

	return 0;
}

示例2:

#include <stdio.h>

int main()
{
	double a;
	a = (double)1/2;
	printf("%lf\n", a);//0.500000

	printf("%lu\n", sizeof(int));//4
	printf("%u\n", (unsigned int)sizeof(int));//4

	int b = 12;
	printf("%lf\n", (double)b);//12.000000


	return 0;
}

发现同样的代码在不同编译器运行结果还不一样
linux上:
在这里插入图片描述

windows QT上:
在这里插入图片描述

程序流程结构

概述

C语言支持最基本的三种程序运行结构:顺序结构、选择结构、循环结构。

  • 顺序结构:程序按顺序执行,不发生跳转。
  • 选择结构:依据是否满足条件,有选择的执行相应功能。
  • 循环结构:依据条件是否满足,循环多次执行某段代码。

选择结构

if语句

注意:
1、果if 语句不加{},则只有后面第一句代码属于if 语句
2、判断时最好变量放常量放左边(防止少写一个等号导致难以找出错误所在)
在这里插入图片描述

在这里插入图片描述

#include <stdio.h>

int main()
{
	int a = 1;
	int b = 2;

	if (a > b)
	{
		printf("%d\n", a);
	}

	return 0;
} 

https://www.bilibili.com/video/BV1jW411K7v2/?spm_id_from=autoNext

if…else语句

在这里插入图片描述

#include <stdio.h>
int main()
{
	int a;
	int b;
	printf("请输入a的值:");
	scanf("%d", &a);
	printf("请输入b的值:");
	scanf("%d", &b);

	if (a > b)
	{
		printf("%d\n", a);
	}
	else
	{
		printf("%d\n", b);
	}
	return 0;
}

结果:
在这里插入图片描述

if…else if…else语句

在这里插入图片描述

#include <stdio.h>

int main()
{
	unsigned int a;
	scanf("%u", &a);

	if (a < 10)
	{
		printf("个位\n");
	}
	else if (a < 100)
	{
		printf("十位\n");
	}
	else if (a < 1000)
	{
		printf("百位\n");
	}
	else
	{
		printf("很大\n");
	}

	return 0;
}

结果:
在这里插入图片描述

三目运算符
#include <stdio.h>

int main()
{
	int a = 10;
	int b = 20;
	int c;

	if (a > b)
	{
		c = a;
	}
	else
	{
		c = b;
	}
	printf("c1 = %d\n", c);//c1 = 20

	a = 1;
	b = 2;
	c = ( a > b ? a : b );
	printf("c2 = %d\n", c);//c2 = 2

	return 0;
}
switch语句(电梯模型)
#include <stdio.h>

int main()
{
	char c;
	c = getchar();

	switch (c) //参数只能是整型变量
	{
	case '1':
		printf("OK\n");
		break;//switch遇到break就中断了
	case '2':
		printf("not OK\n");
		break;
	default://如果上面的条件都不满足,那么执行default
		printf("are u ok?\n");
	}
	return 0;
}

结果:
在这里插入图片描述

循环结构

while语句

在这里插入图片描述

#include <stdio.h>

int main()
{
	int a = 20;
	while (a > 10)
	{
		scanf("%d", &a);
		printf("a = %d\n", a);
	}

	return 0;
}

结果:
在这里插入图片描述

调试
QT的调试

qt creator创建cmake构建的程序,无法启动调试(点左下角运行不出结果 No executable specified.)
在这里插入图片描述
在这里插入图片描述

visual studio的调试

跟qt调试基本上是一样的操作,F9打断点,F5启动调试,F10逐步调试,F11进入函数
在这里插入图片描述

do…while语句

在这里插入图片描述

#include <stdio.h>

int main()
{
	int a = 1;
	do
	{
		a++;
		printf("a = %d\n", a);
	} while (a < 10);

	return 0;
}

结果:

a = 2
a = 3
a = 4
a = 5
a = 6
a = 7
a = 8
a = 9
a = 10

for语句
#include <stdio.h>

int main()
{
	int i;
	int sum = 0;
	for (i = 0; i <= 100; i++)
	{
		sum += i;

	}

	printf("sum = %d\n", sum);//sum = 5050

	return 0;
}

其实也可以这样:
在这里插入图片描述

死循环

在这里插入图片描述

嵌套循环

循环语句之间可以相互嵌套:

#include <stdio.h>

int main()
{
	int num = 0;
	int i, j, k;
	for (i = 0; i < 10; i++)
	{
		for (j = 0; j < 10; j++)
		{
			for (k = 0; k < 10; k++)
			{
				printf("hello world\n");
				num++;
			}
		}
	}

	printf("num = %d\n", num);

	return 0;
}

结果:
在这里插入图片描述

跳转语句break、continue、goto

break语句

在switch条件语句和循环语句中都可以使用break语句:

  • 当它出现在switch条件语句中时,作用是终止某个case并跳出switch结构。
  • 当它出现在循环语句中,作用是跳出当前内循环语句,执行后面的代码。
  • 当它出现在嵌套循环语句中,跳出最近的内循环语句,执行后面的代码。
#include <stdio.h>

int main()
{
	int i = 0;
	while (1)
	{
		i++;
		printf("i = %d\n", i);

		if (i == 10)
		{
			break; //跳出while循环
		}
	}

	int flag = 0;
	int m = 0;
	int n = 0;

	for (m = 0; m < 10; m++)
	{
		for (n = 0; n < 10; n++)
		{
			if (n == 5)
			{
				flag = 1;
				break; //跳出for (n = 0; n < 10; n++)
			}
		}

		if (flag == 1)
		{
			break; //跳出for (m = 0; m < 10; m++)
		}
	}

	return 0;
}

在这里插入图片描述

continue语句

在循环语句中,如果希望立即终止本次循环,并执行下一次循环,此时就需要使用continue语句。

示例:求0-100所有奇数的和

#include<stdio.h>

int main()
{
	int sum = 0;           //定义变量sum

	for (int i = 1; i <= 100; i++)
	{
		if (i % 2 == 0)   //如果i是一个偶数,执行if语句中的代码
		{
			continue;      //结束本次循环
		}
		sum += i;          //实现sum和i的累加
	}

	printf("sum = %d\n", sum);//sum = 2500

	return 0;
}
goto语句(无条件跳转,尽量少用)(注意:有时候去了就回不来了,但是有时候也可让代码精妙绝伦)
#include <stdio.h>

int main()
{
	goto End; //无条件跳转到End的标识
	printf("aaaaaaaaa\n");

End:
	printf("bbbbbbbb\n");

	return 0;
}

在这里插入图片描述

数组和字符串

概述

在程序设计中,为了方便处理数据把具有相同类型的若干变量按有序形式组织起来——称为数组。

数组就是在内存中连续的相同类型的变量空间。同一个数组所有的成员都是相同的数据类型,同时所有的成员在内存中的地址是连续的。
在这里插入图片描述
数组属于构造数据类型:

  • 一个数组可以分解为多个数组元素:这些数组元素可以是基本数据类型或构造类型。
int a[10];  
struct Stu boy[10];
  • 按数组元素类型的不同,数组可分为:数值数组、字符数组、指针数组、结构数组等类别。
int a[10];
char s[10];
char *p[10];

通常情况下,数组元素下标的个数也称为维数,根据维数的不同,可将数组分为一维数组、二维数组、三维数组、四维数组等。通常情况下,我们将二维及以上的数组称为多维数组。

一维数组

一维数组的定义和使用
  • 数组名字符合标识符的书写规定(数字、英文字母、下划线)
  • 数组名不能与其它变量名相同,同一作用域内是唯一的
  • 方括号[]中常量表达式表示数组元素的个数
    在这里插入图片描述
    在这里插入图片描述
    可以使用常量定义数组大小:define SIZE 10
    在这里插入图片描述

示例: 用宏定义常量创建一个数组,并给数组元素赋值

#include <stdio.h>
#define SIZE 10

int main()
{
	int a[SIZE];
	int i;
	for (i = 0; i <10; i ++)
	{
		a[i] = i;
		printf("a[%d] = %d\n", i, a[i]);		
	}
	printf("%d\n", i);
	return 0;
}

在这里插入图片描述

int a[3]表示数组a有3个元素
其下标从0开始计算,因此3个元素分别为a[0],a[1],a[2]

定义数组时[]内最好是常量,使用数组时[]内即可是常量,也可以是变量

示例2:

#include <stdio.h>

int main()
{
	int a[10];//定义了一个数组,名字叫a,有10个成员,每个成员都是int类型
	//a[0]…… a[9],没有a[10]
	//没有a这个变量,a是数组的名字,但不是变量名,它是常量
	a[0] = 0;
	//……
	a[9] = 9;

	int i = 0;
	for (i = 0; i < 10; i++)
	{
		a[i] = i; //给数组赋值
	}

	//遍历数组,并输出每个成员的值
	for (i = 0; i < 10; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");

	return 0;
}

结果:

0 1 2 3 4 5 6 7 8 9 

注意:数组下标越界错误在某些编译器上是不能及时发现的
在这里插入图片描述

一维数组的初始化(初始化:定义的时候同时赋值)

在定义数组的同时进行赋值,称为初始化。全局数组若不初始化,编译器将其初始化为。局部数组若不初始化,内容为随机值

int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//定义一个数组,同时初始化所有成员变量
int a[10] = { 1, 2, 3 };//初始化前三个成员,后面所有元素都设置为0
int a[10] = { 0 };//所有的成员都设置为0

//[]中不定义元素个数,定义时必须初始化
int a[] = { 1, 2, 3, 4, 5 };//定义了一个数组,有5个成员

示例:初始化为随机数

#include <stdio.h>

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

	return 0;
}

结果:

-1553645624
32604
369074640
21996
0
0
369074304
21996
-559922368
32765
数组名(数组名地址是数组首元素地址)
示例1:数组名是一个地址的常量,代表数组中首元素的地址
#include <stdio.h>

int main()
{
	int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
	printf("%p\n", a);
	printf("%p\n", &a);
	printf("%p\n", &a[0]);
	printf("%p\n", &a[1]);
	return 0;
}

在这里插入图片描述

示例2:求数组的长度,同时遍历每个数组元素(c语言没有len函数,用数组大小除以每个数组元素大小得到数组长度 int n = sizeof(a)/sizeof(a[0])
#include <stdio.h>

int main()
{
	int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//定义一个数组,同时初始化所有成员变量

	printf("a = %p\n", a);
	printf("&a[0] = %p\n", &a[0]);

	int n = sizeof(a); //数组占用内存的大小,10个int类型,10 * 4  = 40
	int n0 = sizeof(a[0]);//数组第0个元素占用内存大小,第0个元素为int,4

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

	return 0;
}

在这里插入图片描述

强化训练
1) 一维数组的最值
#include <stdio.h>

int main()
{
	int a[] = {  1, -2, 3,- 4, 5, -6, 7, -8, -9, 10 };//定义一个数组,同时初始化所有成员变量

	int i = 0;
	int max = a[0];
	for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		if (a[i] > max)
		{
			max = a[i];
		}
	}
	printf("数组中最大值为:%d\n", max);//数组中最大值为:10

	return 0;
}
2) 一维数组的逆置(如果数组元素个数为奇数,会多交换一次)
#include <stdio.h>

int main()
{
	int a[] = {  1, -2, 3,- 4, 5, -6, 7, -8, -9, 10 };//定义一个数组,同时初始化所有成员变量

	int i = 0;
	int j = sizeof(a) / sizeof(a[0]) -1;
	int tmp;

	while (i < j)
	{
		tmp = a[i];
		a[i] = a[j];
		a[j] = tmp;
		i++;
		j--;
	}

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

	return 0;
}

结果:

10 -9 -8 7 -6 5 -4 3 -2 1 

3) 冒泡法排序(每次把大的数字冒泡到右边)
#include <stdio.h>

int main()
{
	int a[] = {  1, -2, 3,- 4, 5, -6, 7, -8, -9, 10 };//定义一个数组,同时初始化所有成员变量

	int i = 0;
	int j = 0;
	int n = sizeof(a) / sizeof(a[0]);
	int tmp;

	//1、流程
	//2、试数
	for (i = 0; i < n-1; i++)
	{
		for (j = 0; j < n - i -1 ; j++)//内循环的目的是比较相邻的元素,把大的放到后面
		{
			if (a[j]  > a[j + 1])
			{
				tmp = a[j];
				a[j] = a[j+1];
				a[j+1] = tmp;
			}
		}
	}

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

	return 0;
}

结果:

-9 -8 -6 -4 -2 1 3 5 7 10 

二维数组

二维数组的定义和使用

二维数组定义的一般形式是:

类型说明符 数组名[常量表达式1][常量表达式2]

其中常量表达式1表示第一维下标的长度,常量表达式2 表示第二维下标的长度。

int a[3][4];
  • 命名规则同一维数组

  • 定义了一个三行四列的数组,数组名为a其元素类型为整型,该数组的元素个数为3×4个,即:
    在这里插入图片描述
    二维数组a是按行进行存放的,先存放a[0]行,再存放a[1]行、a[2]行,并且每行有四个元素,也是依次存放的。

  • 二维数组在概念上是二维的:其下标在两个方向上变化,对其访问一般需要两个下标。

  • 在内存中并不存在二维数组,二维数组实际的硬件存储器是连续编址的,也就是说内存中只有一维数组,即放完一行之后顺次放入第二行,和一维数组存放方式是一样的。

#include <stdio.h>

int main()
{
	//定义了一个二维数组,名字叫a
	//由3个一维数组组成,这个一维数组是int [4]
	//这3个一维数组的数组名分别为a[0],a[1],a[2]
	int a[3][4];

	a[0][0] = 0;
	//……
	a[2][3] = 12;

	//给数组每个元素赋值
	int i = 0;
	int j = 0;
	int num = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			a[i][j] = num++;
		}
	}

	//遍历数组,并输出每个成员的值
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			printf("%d, ", a[i][j]);
		}
		printf("\n");
	}

	return 0;
}

结果:

0, 1, 2, 3, 
4, 5, 6, 7, 
8, 9, 10, 11, 
二维数组的初始化
//分段赋值 	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  };

//可以只给部分元素赋初值,未初始化则为0
int a[3][4] = { 1, 2, 3, 4  };

//所有的成员都设置为0
int a[3][4] = {0};

//[]中不定义元素个数,定义时必须初始化(不能直接:int a[][4];,会报错)
int a[][4] = { 1, 2, 3, 4, 5, 6, 7, 8};
示例,数组初始化自动推导(第一维度:sizeof(a)/sizeof(a[0]) 第二维度:sizeof(a[0])/sizeof(a[0][0])
#include <stdio.h>

int main()
{
	int a[][4] = {1,2,3,4,5,6,7,8};

	int n = sizeof(a)/sizeof(a[0][0]);
	printf("%d\n", n);//8

	printf("%ld\n", sizeof(a)/sizeof(a[0]));//2
	printf("%ld\n", sizeof(a[0])/sizeof(a[0][0]));//4

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

	return 0;
}

结果:
在这里插入图片描述

数组名

数组名是一个地址的常量,代表数组中首元素的地址。

#include <stdio.h>

int main()
{
	//定义了一个二维数组,名字叫a
	//二维数组是本质上还是一维数组,此一维数组有3个元素
//每个元素又是一个一维数组int[4]
	int a[3][4] = { 1, 2, 3, 4 , 5, 6, 7, 8, 9, 10, 11, 12  };

	//数组名为数组首元素地址,二维数组的第0个元素为一维数组
	//第0个一维数组的数组名为a[0]
	printf("a = %p\n", a);//a = 0x7ffe7750a0a0
	printf("a[0] = %p\n", a[0]);//a[0] = 0x7ffe7750a0a0
	
	//测二维数组所占内存空间,有3个一维数组,每个一维数组的空间为4*4
	//sizeof(a) = 3 * 4 * 4 = 48
	printf("sizeof(a) = %ld\n", sizeof(a));//sizeof(a) = 48

	//测第0个元素所占内存空间,a[0]为第0个一维数组int[4]的数组名,4*4=16
	printf("sizeof(a[0]) = %ld\n", sizeof(a[0]) );//sizeof(a[0]) = 16

	//测第0行0列元素所占内存空间,第0行0列元素为一个int类型,4字节
	printf("sizeof(a[0][0]) = %ld\n", sizeof(a[0][0]));//sizeof(a[0][0]) = 4

	//求二维数组行数
	printf("i = %ld\n", sizeof(a) / sizeof(a[0]));//i = 3

	// 求二维数组列数
	printf("j = %ld\n", sizeof(a[0]) / sizeof(a[0][0]));//j = 4

	//求二维数组行*列总数
	printf("n = %ld\n", sizeof(a) / sizeof(a[0][0]));//n = 12

	return 0;
}
强化训练
#include <stdio.h>

int main()
{
	//二维数组:  五行、三列
	//行代表人:  老大到老五
	//列代表科目:语、数、外
	float a[5][3] = { { 80, 75, 56 }, { 59, 65, 71 }, { 59, 63, 70 }, { 85, 45, 90 }, { 76, 77, 45 } };

	int i, j, person_low[3] = { 0 };
	float s = 0, lesson_aver[3] = { 0 };

	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			s = s + a[j][i];
			if (a[j][i] < 60)
			{
				person_low[i]++;
			}
		}

		lesson_aver[i] = s / 5;
		s = 0;
	}

	printf("各科的平均成绩:\n");
	for (i = 0; i < 3; i++)
	{
		printf("%.2f\n", lesson_aver[i]);
	}
		
	printf("各科不及格的人数:\n");
	for (i = 0; i < 3; i++)
	{
		printf("%d\n", person_low[i]);
	}
		
	return 0;
}

结果:
在这里插入图片描述

多维数组(了解)

多维数组的定义与二维数组类似,其语法格式具体如下:

数组类型修饰符 数组名 [n1][n2][nn];
int a[3][4][5];

定义了一个三维数组,数组的名字是a,数组的长度为3,每个数组的元素又是一个二维数组,这个二维数组的长度是4,并且这个二维数组中的每个元素又是一个一维数组,这个一维数组的长度是5,元素类型是int。

#include <stdio.h>

int main()
{
	//int a[3][4][5] ;//定义了一个三维数组,有3个二维数组int[4][5]
	int a[3][4][5] = { { { 1, 2, 3, 4, 5 }, { 6, 7, 8, 9, 10 }, { 0 }, { 0 } }, { { 0 }, { 0 }, { 0 }, { 0 } }, { { 0 }, { 0 }, { 0 }, { 0 } } };

	int i, j, k;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			for (k = 0; k < 5; k++)
			{
				//添加访问元素代码
				printf("%d, ", a[i][j][k]);
			}
			printf("\n");
		}
	}
	return 0;
}

在这里插入图片描述

字符数组char a[]与字符串 char a[, , , , , ... , '\0']

字符数组与字符串区别
  • C语言中没有字符串这种数据类型,可以通过char的数组来替代;
  • 字符串一定是一个char的数组,但char的数组未必是字符串;
  • 数字0(和字符‘\0’等价)结尾的char数组就是一个字符串,但如果char数组没有以数字0结尾,那么就不是一个字符串,只是普通字符数组,所以字符串是一种特殊的char的数组。

用%s打印字符数组时,会给它一个字符数组名地址,然后系统就一个字符一个字符打印,遇到 '\0'就结束,如果没有结束字符,后面打印就会出现一堆乱码(\0就是数字0

#include <stdio.h>

int main()
{
	char c1[] = { 'c', ' ', 'p', 'r', 'o', 'g' }; //普通字符数组
	printf("c1 = %s\n", c1); //c1 = c progUڋ //乱码,因为没有’\0’结束符

	//以‘\0’(‘\0’就是数字0)结尾的字符数组是字符串
	char c2[] = { 'c', ' ', 'p', 'r', 'o', 'g', '\0'}; 
	printf("c2 = %s\n", c2);//c2 = c prog

	//字符串处理以‘\0’(数字0)作为结束符,后面的'h', 'l', 'l', 'e', 'o'不会输出
	char c3[] = { 'c', ' ', 'p', 'r', 'o', 'g', '\0', 'h', 'l', 'l', 'e', 'o', '\0'};
	printf("c3 = %s\n", c3);//c3 = c prog

	return 0;
}

注意事项:
{‘a’, ‘b’, ‘c’, '\0'}{‘a’, ‘b’, ‘c’, 0}相当于 “abc”
在这里插入图片描述

字符串的初始化
#include <stdio.h>

// C语言没有字符串类型,通过字符数组模拟
// C语言字符串,以字符‘\0’结束, 就是数字0
int main()
{
	//不指定长度, 没有0结束符,有多少个元素就有多长
	char buf[] = { 'a', 'b', 'c' };
	printf("buf = %s\n", buf);//abc̊XP	//乱码(有的编译器不会出现乱码,但不代表能这样用)

	//指定长度,后面没有赋值的元素,自动补0
	char buf2[100] = { 'a', 'b', 'c' };
	printf("buf2 = %s\n", buf2);//buf2 = abc

	//所有元素赋值为0
	char buf3[100] = { 0 };

	//char buf4[2] = { '1', '2', '3' };//数组越界

	char buf5[50] = { '1', 'a', 'b', '0', '7' };
	printf("buf5 = %s\n", buf5);//buf5 = 1ab07

	char buf6[50] = { '1', 'a', 'b', 0, '7' };
	printf("buf6 = %s\n", buf6);//buf6 = 1ab

	char buf7[50] = { '1', 'a', 'b', '\0', '7' };
	printf("buf7 = %s\n", buf7);//buf7 = 1ab

	//使用字符串初始化,编译器自动在后面补0,常用
	char buf8[] = "agjdslgjlsdjg";

	//'\0'后面最好不要连着数字,有可能几个数字连起来刚好是一个转义字符
	//'\ddd'八进制字义字符,'\xdd'十六进制转义字符
	// \012相当于\n
	char str[] = "\012abc";
	printf("str == %s\n", str);

	return 0;
}

最后一个结果:
在这里插入图片描述

字符串的输入输出

由于字符串采用了’\0’标志,字符串的输入输出将变得简单方便。

#include <stdio.h>

int main()
{
	char str[100];
   
	printf("input string1 : \n");
	scanf("%s", str);//scanf(“%s”,str)默认以空格分隔
	printf("output:%s\n", str);

	return 0;
}

在这里插入图片描述

强化训练:字符串追加(重要)
#include <stdio.h>

int main()
{
	char str1[] = "abcdef";
	char str2[] = "123456";
	char dst[13];

	int i = 0;
	while (str1[i] != 0)
	{
		dst[i] = str1[i];
		i++;
	}

	int j = 0;
	while (str2[j] != 0)
	{
		dst[i + j] = str2[j];
		j++;
	}
	dst[i + j] = 0; //字符串结束符(也可写成'\0')

	printf("dst = %s\n", dst);

	printf("\n");

	printf("%ld\n", sizeof(dst));
	printf("%ld\n", sizeof(dst[0]));

	printf("\n");

	for(int k = 0; k < sizeof(dst)/sizeof(dst[0]); k++)
	{
		printf("%c\n", dst[k]);	
		printf("%d\n", dst[k]);	
		printf("\n");
	}

	return 0;
}

结果:

dst = abcdef123456

13
1

a
97

b
98

c
99

d
100

e
101

f
102

1
49

2
50

3
51

4
52

5
53

6
54


0


注意:结束符就是数字零

函数的调用:产生随机数(用系统时间做种子产生随机数 time_t time(NULL) rand() srand()

当调用函数时,需要关心5要素:

  • 头文件:包含指定的头文件
  • 函数名字:函数名字必须和头文件声明的名字一样
  • 功能:需要知道此函数能干嘛后才调用
  • 参数:参数类型要匹配
  • 返回值:根据需要接收返回值
#include <time.h>
time_t time(time_t *t);
功能:获取当前系统时间
参数:常设置为NULL
返回值:当前系统时间, time_t 相当于long类型,单位为毫秒

#include <stdlib.h>
void srand(unsigned int seed);
功能:用来设置rand()产生随机数时的随机种子
参数:如果每次seed相等,rand()产生随机数相等
返回值:无

#include <stdlib.h>
int rand(void);
功能:返回一个随机数值
参数:无
返回值:随机数
#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int main()
{
	time_t tm = time(NULL);//得到系统时间
	printf("%ld\n", sizeof(tm));//8
	srand((unsigned int)tm);//随机种子只需要设置一次即可

	int r = rand();
	printf("r = %d\n", r);

	return 0;
}

在这里插入图片描述

字符串处理函数
1) gets() (从键盘获取输入字符【可含空格】,遇换行符结束)(被抛弃了)
#include <stdio.h>
char *gets(char *s);
功能:从标准输入读入字符,并保存到s指定的内存空间,直到出现换行符或读到文件结尾为止。
参数:
	s:字符串首地址
返回值:
	成功:读入的字符串
	失败:NULL

gets(str)与scanf(“%s”,str)的区别:

  • gets(str)允许输入的字符串含有空格
  • scanf(“%s”,str)不允许含有空格

注意:由于scanf()和gets()无法知道字符串s大小,必须遇到换行符或读到文件结尾为止才接收输入,因此容易导致字符数组越界(缓冲区溢出)的情况。

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int main()
{
	char str[100];
	printf("请输入str: ");
	gets(str);
	printf("str = %s\n", str);
	return 0;
}

结果:在这里插入图片描述

2) fgets()(可指定字符数获取键盘输入字符,不够的话会取到换行符,超出字符的话只截取指定字符数)
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
功能:从stream指定的文件内读入字符,保存到s所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 '\0' 作为字符串结束。
参数:
	s:字符串
	size:指定最大读取字符串的长度(size - 1)
	stream:文件指针,如果读键盘输入的字符串,固定写为stdin
返回值:
	成功:成功读取的字符串
	读到文件尾或出错: NULL

fgets()在读取一个用户通过键盘输入的字符串的时候,同时把用户输入的回车也做为字符串的一部分。通过scanf和gets输入一个字符串的时候,不包含结尾的“\n”,但通过fgets结尾多了“\n”。fgets()函数是安全的,不存在缓冲区溢出的问题。

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int main()
{
	char str[10];
	printf("%ld\n", sizeof(str));//10,只会输出9个字符,留一个放结束符0
	printf("请输入str: ");
	fgets(str, sizeof(str), stdin);
	printf("str = \"%s\"\n", str);
}

在这里插入图片描述

3) puts()(输出字符串,能自动换行,但不能像printf那样用%格式化其他数据类型再输出)
#include <stdio.h>
int puts(const char *s);
功能:标准设备输出s字符串,在输出完成后自动输出一个'\n'。
参数:
	s:字符串首地址
返回值:
	成功:非负数
	失败:-1
#include <stdio.h>

int main()
{
	printf("hello world\n");
	puts("hello world");
	return 0;
}

在这里插入图片描述

4) fputs()(也是输出内容,但不止输出到屏幕,也能指定输出到某个文件)
#include <stdio.h>
int fputs(const char * str, FILE * stream);
功能:将str所指定的字符串写入到stream指定的文件中, 字符串结束符 '\0'  不写入文件。 
参数:
	str:字符串
	stream:文件指针,如果把字符串输出到屏幕,固定写为stdout
返回值:
	成功:0
	失败:-1

fputs()是puts()的文件操作版本,但fputs()不会自动输出一个’\n’。

#include <stdio.h>

int main()
{    
	printf("hello world\n");
	puts("hello world");
	fputs("hello world\n", stdout);
}

结果:
在这里插入图片描述

5) strlen()(计算字符串长度,但遇到\0会结束,感觉还不如用sizeof呢)
#include <string.h>
size_t strlen(const char *s);
功能:计算指定指定字符串s的长度,不包含字符串结束符‘\0’
参数:
s:字符串首地址
返回值:字符串s的长度,size_tunsigned int类型
#include <stdio.h>
#include <string.h>

int main()
{
	char str[] = "abcdefg";
	printf("%ld\n", sizeof(str));//8

	int n = strlen(str);
	printf("n = %d\n", n);//n = 7

	char str2[] = "\0abcdefg";	
	printf("%ld\n", sizeof(str2));//9
	printf("n = %ld\n", strlen(str2));//n = 0
}
6) strcpy(拷贝字符串,遇到结束符\0将停止拷贝,不能指定拷贝字符串长度)
#include <string.h>
char *strcpy(char *dest, const char *src);
功能:把src所指向的字符串复制到dest所指向的空间中,'\0'也会拷贝过去
参数:
	dest:目的字符串首地址
	src:源字符首地址
返回值:
	成功:返回dest字符串的首地址
	失败:NULL

注意:如果参数dest所指的内存空间不够大,可能会造成缓冲溢出的错误情况。(在visual studio中会表现出来)

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

int main()
{
	char src[] = "abcdefg\0dgf3";
	char dst[20];
	strcpy(dst, src);
	printf("%s\n", dst);//abcdefg
	printf("%d %c\n", dst[6],dst[6]);//103 g
	printf("%d %c\n", dst[7],dst[7]);//0
	printf("%d %c\n", dst[8],dst[8]);//-64(不知道为什么一直是这个结果,结束符后面不都是随机的吗?)
	printf("%d %c\n", dst[9],dst[9]);//随机
	printf("%d %c\n", dst[10],dst[10]);//随机
	return 0;
}

结果:
在这里插入图片描述

7) strncpy()(拷贝字符串,遇到结束符\0将停止拷贝,能指定拷贝字符串长度)
#include <string.h>
char *strncpy(char *dest, const char *src, size_t n);
功能:把src指向字符串的前n个字符复制到dest所指向的空间中,是否拷贝结束符看指定的长度是否包含'\0'。
参数:
	dest:目的字符串首地址
	src:源字符首地址
	n:指定需要拷贝字符串个数
返回值:
	成功:返回dest字符串的首地址
	失败:NULL
#include <stdio.h>
#include <string.h>

int main()
{
	char src[] = "abcdefg\0";
	char dst[20];
	char dst2[20];
	strncpy(dst, src, strlen(src)+1);
	strncpy(dst2, src, sizeof(src));
	printf("%s\n", dst);
	printf("%s\n", dst2);
	return 0;
}

在这里插入图片描述

8) strcat()(字符串追加)
#include <string.h>
char *strcat(char *dest, const char *src);
功能:将src字符串连接到dest的尾部,‘\0’也会追加过去
参数:
	dest:目的字符串首地址
	src:源字符首地址
返回值:
	成功:返回dest字符串的首地址
	失败:NULL

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

int main()
{
	char str[20] = "123";
	char *src = "hello world";
	printf("%s\n", strcat(str, src));
	return 0;
}

结果:

123hello world
9) strncat()(字符串追加,n个字符)
#include <string.h>
char *strncat(char *dest, const char *src, size_t n);
功能:将src字符串前n个字符连接到dest的尾部,‘\0’也会追加过去
参数:
	dest:目的字符串首地址
	src:源字符首地址
	n:指定需要追加字符串个数
返回值:
	成功:返回dest字符串的首地址
	失败:NULL

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

int main()
{
	char str[20] = "123";
	char *src = "hello world";
	printf("%s\n", strncat(str, src, 7));
	return 0;
}
123hello w
10) strcmp()(按ascii码逐个比较字符串中的字符)
#include <string.h>
int strcmp(const char *s1, const char *s2);
功能:比较 s1 和 s2 的大小,比较的是字符ASCII码大小。
参数:
	s1:字符串1首地址
	s2:字符串2首地址
返回值:
	相等:0
	大于:>0
	小于:<0
	
#include <stdio.h>
#include <string.h>

int main()
{
	char *str1 = "hello world";
	char *str2 = "hello mike";

	if (strcmp(str1, str2) == 0)
	{
	printf("str1==str2\n");
	}
	else if (strcmp(str1, str2) > 0)
	{
		printf("str1>str2\n");
	}	
	else
	{
		printf("str1<str2\n");
	}
	return 0;
}

结果:

str1>str2
11) strncmp()(按ascii码逐个比较字符串中的前n个字符)
#include <string.h>
int strncmp(const char *s1, const char *s2, size_t n);
功能:比较 s1 和 s2 前n个字符的大小,比较的是字符ASCII码大小。
参数:
	s1:字符串1首地址
	s2:字符串2首地址
	n:指定比较字符串的数量
返回值:
	相等:0
	大于: > 0
	小于: < 0
#include <stdio.h>
#include <string.h>

int main()
{
	char *str1 = "hello world";
	char *str2 = "hello mike";
	
	int i = strncmp(str1, str2, 7);
	if (i == 0)
	{
		printf("str1==str2\n");
	}
	else if (i > 0)
	{
		printf("str1>str2\n");
	}
	else
	{
		printf("str1<str2\n");
	}
	return 0;
}

结果:

str1>str2
12) sprintf()(将格式化字符串输出到指定数组,返回值是实际格式化字符个数,失败返回 -1)(组包)
#include <stdio.h>
int sprintf(char *str, const char *format, ...);
功能:根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中,直到出现字符串结束符 '\0'  为止。
参数:
	str:字符串首地址
	format:字符串格式,用法和printf()一样
返回值:
	成功:实际格式化的字符个数
	失败: - 1
#include <stdio.h>
#include <string.h>

int main()
{
	char dst[100] = { 0 };
	int a = 10;
	char src[] = "hello world";
	printf("a = %d, src = %s", a, src);
	printf("\n");
	
	int len = sprintf(dst, "a = %d, src = %s", a, src);
	printf("dst = \"%s\"\n", dst);
	printf("len = %d\n", len);
	return 0;
}

结果:

a = 10, src = hello world
dst = "a = 10, src = hello world"
len = 25
13) sscanf()(从字符串中以指定格式提取字符,提取字符串%s默认以空格分割,以逗号分割不行,提取数字%d可以以逗号分割)(拆包)
#include <stdio.h>
int sscanf(const char *str, const char *format, ...);
功能:从str指定的字符串读取数据,并根据参数format字符串来转换并格式化数据。
参数:
	str:指定的字符串首地址
	format:字符串格式,用法和scanf()一样
返回值:
	成功:参数数目,成功转换的值的个数
	失败: - 1
#include <stdio.h>
#include <string.h>

int main()
{
	char src[] = "a=10, b=20";
	int a;
	int b;
	sscanf(src, "a=%d,  b=%d", &a, &b);
	printf("a:%d, b:%d\n", a, b);

	char src2[] = "sdf dom mike";
	char a1[10];
	char b1[10];
	char c1[10];
	sscanf(src2, "%s %s %s", a1, b1, c1);//不能取地址&
	printf("%s %s %s\n", a1, b1, c1 );
	return 0;
}

结果:

a:10, b:20
sdf dom mike
14) strchr()(字符类型查询)(int c 指字符)
#include <string.h>
char *strchr(const char *s, int c);
功能:在字符串s中查找字母c出现的位置
参数:
	s:字符串首地址
	c:匹配字母(字符)
返回值:
	成功:返回第一次出现的c地址
	失败:NULL
#include <stdio.h>
#include <string.h>

int main()
{
	char src[] = "ddda123abcd";
	char *p = strchr(src, 'a');
	printf("p = %s\n", p);//p = a123abcd

	return 0;
}
15) strstr()(字符串类型查询)(char * 指字符串)
#include <string.h>
char *strstr(const char *haystack, const char *needle);
功能:在字符串haystack中查找字符串needle出现的位置
参数:
	haystack:源字符串首地址
	needle:匹配字符串首地址
返回值:
	成功:返回第一次出现的needle地址
	失败:NULL
	#include <stdio.h>
#include <string.h>

int main()
{
	char src[] = "ddddabcd123abcd333abcd";
	char *p = strstr(src, "abcd");
	printf("p = %s\n", p);//p = abcd123abcd333abcd

	return 0;
}
16) strtok()(字符串切割,一个一个返回,要用循环一个一个获取)(会破坏原来字符串,需要复制原字符串后使用)
#include <string.h>
char *strtok(char *str, const char *delim);
功能:来将字符串分割成一个个片段。当strtok()在参数s的字符串中发现参数delim中包含的分割字符时, 则会将该字符改为\0 字符,当连续出现多个时只替换第一个为\0。
参数:
	str:指向欲分割的字符串
	delim:为分割字符串中包含的所有字符
返回值:
	成功:分割后字符串首地址
	失败:NULL
  • 在第一次调用时:strtok()必需给予参数s字符串
  • 往后的调用则将参数s设置成NULL,每次调用成功则返回指向被分割出片段的指针
#include <stdio.h>
#include <string.h>

int main()
{
	char a[100] = "adc*fvcv*ebcy*hghbdfg*casdert";
	char temp[100];
	strcpy(temp, a);

	char *s = strtok(temp, "*");//将"*"分割的子串取出
	while (s != NULL)
	{
		printf("%s\n", s);
		s = strtok(NULL, "*");
	}

	return 0;
}

结果:

adc
fvcv
ebcy
hghbdfg
casdert
17) atoi() atof() atol()(将字符串转换成整型、浮点、长整型)
#include <stdlib.h>
int atoi(const char *nptr);
功能:atoi()会扫描nptr字符串,跳过前面的空格字符,直到遇到数字或正负号才开始做转换,而遇到非数字或字符串结束符('\0')才结束转换,并将结果返回返回值。
参数:
	nptr:待转换的字符串
返回值:成功转换后整数

类似的函数有:

  • atof():把一个小数形式的字符串转化为一个浮点数。
  • atol():将一个字符串转化为long类型
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
	char str1[] = " -10ds hjk 55";
	int num1 = atoi(str1);
	printf("num1 = %d\n", num1);
	
	char str2[] = "0.123";
	double num2 = atof(str2);
	printf("num2 = %lf\n", num2);

	return 0;
}

结果:
在这里插入图片描述

函数

概述

函数分类

C 程序是由函数组成的,我们写的代码都是由主函数 main()开始执行的。函数是 C 程序的基本模块,是用于完成特定任务的程序代码单元。

从函数定义的角度看,函数可分为系统函数和用户定义函数两种:

  • 系统函数,即库函数:这是由编译系统提供的,用户不必自己定义这些函数,可以直接使用它们,如我们常用的打印函数printf()。
  • 用户定义函数:用以解决用户的专门需要。
函数的作用
  • 函数的使用可以省去重复代码的编写,降低代码重复率
// 求两数的最大值
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int max(int a, int b)
{
	if (a > b){
		return a;
	}
	else{
		return b;
	}
}

int main()
{
	// 操作1 ……
	// ……
	int a1 = 10, b1 = 20, c1 = 0;
	c1 = max(a1, b1); // 调用max()
	printf("%d\n", c1);//20

	// 操作2 ……
	// ……
	int a2 = 11, b2 = 21, c2 = 0;
	c2 = max(a2, b2); // 调用max()
	printf("%d\n", c2);21

	// ……

	return 0;
}
  • 函数可以让程序更加模块化,从而有利于程序的阅读,修改和完善

假如我们编写一个实现以下功能的程序:读入一行数字;对数字进行排序;找到它们的平均值;打印出一个柱状图。如果我们把这些操作直接写在main()里,这样可能会给用户感觉代码会有点凌乱。但,假如我们使用函数,这样可以让程序更加清晰、模块化:

#include <stdio.h>

int main()
{
	float list[50];

	// 这里只是举例,函数还没有实现
	readlist(list, 50);
	sort(list, 50);
	average(list, 50);
	bargraph(list, 50);

	return 0;
}

这里我们可以这么理解,程序就像公司,公司是由部门组成的,这个部门就类似于C程序的函数。默认情况下,公司就是一个大部门( 只有一个部门的情况下 ),相当于C程序的main()函数。如果公司比较小( 程序比较小 ),因为任务少而简单,一个部门即可( main()函数 )胜任。但是,如果这个公司很大( 大型应用程序 ),任务多而杂,如果只是一个部门管理( 相当于没有部门,没有分工 ),我们可想而知,公司管理、运营起来会有多混乱,不是说这样不可以运营,只是这样不完美而已,如果根据公司要求分成一个个部门( 根据功能封装一个一个函数 ),招聘由行政部门负责,研发由技术部门负责等,这样就可以分工明确,结构清晰,方便管理,各部门之间还可以相互协调。

函数的定义

函数定义格式

函数定义的一般形式:

返回类型 函数名(形式参数列表)
{
	数据定义部分;
	执行语句部分;
}

在这里插入图片描述

函数名字、形参、函数体、返回值
1) 函数名

理论上是可以随意起名字,最好起的名字见名知意,应该让用户看到这个函数名字就知道这个函数的功能。注意,函数名的后面有个圆换号(),代表这个为函数,不是普通的变量名。

2) 形参列表

在定义函数时指定的形参,在未出现函数调用时,它们并不占内存中的存储单元,因此称它们是形式参数或虚拟参数,简称形参,表示它们并不是实际存在的数据,所以,形参里的变量不能赋值。

void max(int a = 10, int b = 20) // error, 形参不能赋值
{
}

在定义函数时指定的形参,必须是,类型+变量的形式:

//1: right, 类型+变量
void max(int a, int b)
{
}

//2: error, 只有类型,没有变量
void max(int, int)
{
}

//3: error, 只有变量,没有类型
int a, int b;
void max(a, b)
{
}

在定义函数时指定的形参,可有可无,根据函数的需要来设计,如果没有形参,圆括号内容为空,或写一个void关键字:

// 没形参, 圆括号内容为空
void max()
{
}

// 没形参, 圆括号内容为void关键字
void max(void)
{
}
3) 函数体

花括号{ }里的内容即为函数体的内容,这里为函数功能实现的过程,这和以前的写代码没太大区别,以前我们把代码写在main()函数里,现在只是把这些写到别的函数里。

4) 返回值

函数的返回值是通过函数中的return语句获得的,return后面的值也可以是一个表达式。
a)尽量保证return语句中表达式的值和函数返回类型是同一类型。

int max() // 函数的返回值为int类型
{
	int a = 10;
	return a;// 返回值a为int类型,函数返回类型也是int,匹配
}

b)如果函数返回的类型和return语句中表达式的值不一致,则以函数返回类型为准,即函数返回类型决定返回值的类型。对数值型数据,可以自动进行类型转换。

double max() // 函数的返回值为double类型
{
	int a = 10;
	return a;// 返回值a为int类型,它会转为double类型再返回
}

注意:如果函数返回的类型和return语句中表达式的值不一致,而它又无法自动进行类型转换,程序则会报错。

c)return语句的另一个作用为中断return所在的执行函数,类似于break中断循环、switch语句一样。

int max()
{
	return 1;// 执行到,函数已经被中断,所以下面的return 2无法被执行到
	return 2;// 没有执行
}

d)如果函数带返回值,return后面必须跟着一个值,如果函数没有返回值,函数名字的前面必须写一个void关键字,这时候,我们写代码时也可以通过return中断函数(也可以不用),只是这时,return后面不带内容( 分号“;”除外)

void max()// 最好要有void关键字
{
	return; // 中断函数,这个可有可无
}

函数的调用

定义函数后,我们需要调用此函数才能执行到这个函数里的代码段。这和main()函数不一样,main()为编译器设定好自动调用的主函数,无需人为调用,我们都是在main()函数里调用别的函数,一个 C 程序里有且只有一个main()函数。

函数执行流程
#include <stdio.h>

void print_test()
{
	printf("this is for test\n");
}

int main()
{
	print_test();	// print_test函数的调用

	return 0;
}
1)进入main()函数
2)调用print_test()函数:

a.它会在main()函数的前面寻找有没有一个名字叫“print_test”的函数定义;
b.如果找到,接着检查函数的参数,这里调用函数时没有传参,函数定义也没有形参,参数类型匹配;
c.开始执行print_test()函数,这时候,main()函数里面的执行会阻塞( 停 )在print_test()这一行代码,等待print_test()函数的执行。

3)print_test()函数执行完( 这里打印一句话 ),main()才会继续往下执行,执行到return 0, 程序执行完毕。
函数的形参和实参
  • 形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用。
  • 实参出现在主调函数中,进入被调函数后,实参也不能使用。
  • 实参变量对形参变量的数据传递是“值传递”,即单向传递,只由实参传给形参,而不能由形参传回来给实参。
  • 在调用函数时,编译系统临时给形参分配存储单元。调用结束后,形参单元被释放。
  • 实参单元与形参单元是不同的单元。调用结束后,形参单元被释放,函数调用结束返回主调函数后则不能再使用该形参变量。实参单元仍保留并维持原值。因此,在执行一个被调用函数时,形参的值如果发生改变,并不会改变主调函数中实参的值。
无参函数调用

如果是调用无参函数,则不能加上“实参”,但括号不能省略。

// 函数的定义
void test()
{
}

int main()
{
	// 函数的调用
	test();	// right, 圆括号()不能省略
	test(250); // error, 函数定义时没有参数

return 0;
}
有参函数调用

a)如果实参表列包含多个实参,则各参数间用逗号隔开。

// 函数的定义
void test(int a, int b)
{
}

int main()
{
	int p = 10, q = 20;
	test(p, q);	// 函数的调用

	return 0;
}

b)实参与形参的个数应相等,类型应匹配(相同或赋值兼容)。实参与形参按顺序对应,一对一地传递数据。
在这里插入图片描述
c)实参可以是常量、变量或表达式,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。所以,这里的变量是在圆括号( )外面定义好、赋好值的变量。

// 函数的定义
void test(int a, int b)
{
}

int main()
{
	// 函数的调用
	int p = 10, q = 20;
	test(p, q);	// right
	test(11, 30 - 10); // right

	test(int a, int b); // error, 不应该在圆括号里定义变量

	return 0;
}
函数返回值 return

a)如果函数定义没有返回值,函数调用时不能写void关键字,调用函数时也不能接收函数的返回值。

// 函数的定义
void test()
{
}

int main()
{
	// 函数的调用
	test(); // right
	void test(); // error, void关键字只能出现在定义,不可能出现在调用的地方
	int a = test();	// error, 函数定义根本就没有返回值

	return 0;
}

b)如果函数定义有返回值,这个返回值我们根据用户需要可用可不用,但是,假如我们需要使用这个函数返回值,我们需要定义一个匹配类型的变量来接收。

// 函数的定义, 返回值为int类型
int test()
{
}

int main()
{
	// 函数的调用
	int a = test(); // right, a为int类型(visual studio会报错,说函数必须返回一个值)
	int b;
	b = test();	// right, 和上面等级

	char *p = test(); // 虽然调用成功没有意义, p为char *, 函数返回值为int, 类型不匹配

	// error, 必须定义一个匹配类型的变量来接收返回值
	// int只是类型,没有定义变量
	int = test();	
	
	return 0;
}

函数的声明

如果使用用户自己定义的函数,而该函数与调用它的函数(即主调函数)不在同一文件中,或者函数定义的位置在主调函数之后,则必须在调用此函数之前对被调用的函数作声明。

所谓函数声明,就是在函数尚在未定义的情况下,事先将该函数的有关信息通知编译系统,相当于告诉编译器,函数在后面定义,以便使编译能正常进行。

注意:一个函数只能被定义一次,但可以声明多次

1、声明时可在前面加extern关键字,加不加没有区别

2、声明不一定必须要放在main函数前,在使用前一句代码声明也是可以的(声明直接把函数定义的第一行代码后面加分号即可)

3、声明函数里的形参变量名可以跟定义的形参变量名不一样(甚至形参变量名都可以不写)

#include <stdio.h>

extern int max(int, int); // 函数的声明,分号不能省略
// int max(int, int); // 另一种方式

int main()
{
	int a = 10, b = 25, num_max = 0;

	int max(int gg, int sffd);//可在使用前声明,形参变量名随意
	num_max = max(a, b); // 函数的调用

	printf("num_max = %d\n", num_max);

	return 0;
}

// 函数的定义
int max(int x, int y)
{
	return x > y ? x : y;
}

结果:

num_max = 25
函数定义和声明的区别:

1)定义是指对函数功能的确立,包括指定函数名、函数类型、形参及其类型、函数体等,它是一个完整的、独立的函数单位。
2)声明的作用则是把函数的名字、函数类型以及形参的个数、类型和顺序(注意,不包括函数体)通知编译系统,以便在对包含函数调用的语句进行编译时,据此对其进行对照检查(例如函数名是否正确,实参与形参的类型和个数是否一致)。

main函数与exit函数

在main函数中调用exit和return结果是一样的,但在子函数中调用return只是代表子函数终止了,在子函数中调用exit,那么程序终止。

#include <stdio.h>
#include <stdlib.h>

void fun()
{
	printf("fun\n");
	//return;
	exit(0);//exit什么数字这里结果都一样,具体不同数字含义学了系统编程再讲
}

int main()
{
	fun();
	printf("here\n");
	while (1);

	return 0;
}

结果:
在这里插入图片描述

多文件(分文件)编程

分文件编程
  • 把函数声明放在头文件xxx.h中,在主函数中包含相应头文件
  • 在头文件对应的xxx.c中实现xxx.h声明的函数

在这里插入图片描述
示例:
在这里插入图片描述
main.c

//#include <stdio.h>
//#include <stdlib.h>
#include "my_add.h"
//void my_add(int, int);//可以不用指定哪个.c文件而直接找到函数,所以一般项目中不允许存在同名函数
int main()
{
	my_add(1, 2);
	return 0;
}	

my_add.c

#include <stdio.h>
void my_add(int a, int b)
{
	printf("%d\n", a + b);
}

my_add.h

void my_add(int, int);

运行指令gcc *.c -o test编译并执行:
在这里插入图片描述

注意:
1、同个项目的多个.c文件中,不能出现同名函数(static静态函数除外)
2、.c文件是用来定义函数的,.h头文件是用来声明函数的

防止头文件重复包含

当一个项目比较大时,往往都是分文件,这时候有可能不小心把同一个头文件 include 多次,或者头文件嵌套包含。

a.h 中包含 b.h :

#include "b.h"

b.h 中包含 a.h:

#include "a.h"

main.c 中使用其中头文件:

#include "a.h"

int main()
{
	return 0;
}

编译上面的例子,会出现如下错误:
在这里插入图片描述
为了避免同一个文件被include多次,C/C++中有两种方式,一种是 #ifndef 方式,一种是 #pragma once 方式。
方法一:

#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__

// 声明语句

#endif

关于下划线,说是一种编程风格,用不用,用几个,都不影响(意思是如果文件名为somefile.h,你可以写成SOMEFILE_H,也可以_SOMEFILE_H_,也可以_SOMEFILE_H等等)
其实,貌似只要上面下面相同,写啥都无所谓,都能实现功能:
在这里插入图片描述

参考文章1:#ifndef详解
参考文章2:#ifndef STDIO_H 中的下划线含义分别是什么哦

方法二:
将这个放在.h头文件最上面

#pragma once

// 声明语句
怎么看预处理后的文件(gcc -E main.c -o main.i
gcc -E main.c -o main.i

在这里插入图片描述

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dontla

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值