c语言基础(五)

202 阅读3分钟

1. 二维数组:

	int arr[10] = {1,2,3,5,6,7};

	{1,2,3,5,6,7};
	{1,2,3,5,6,7};
	{1,2,3,5,6,7};
	{1,2,3,5,6,7};

	// 定义语法:

	int arr[2][3] = 
		{
		{2, 5, 8},
		{7, 9 10}
		};

	int arr[3][5] = {{2, 3, 54, 56, 7 }, {2, 67, 4, 35, 9}, {1, 4, 16, 3, 78}};

	// 打印:
		for(i = 0; i < 3; i++)		// 行
		{
			for(j = 0; j <5; j++)   // 列
			{
				printf("%d ", arr[i][j]);
			}
			printf("\n");
		}

定义二维数组的时候,写成矩阵的形式更加的易于理解;二维数组的遍历是先行后列的。

2. 数组相关的大小:

	// 数组大小: 
        sizeof(arr);

	// 一行大小: 
        sizeof(arr[0]) // 二维数组的一行,就是一个一维数组。

	// 一个元素大小:
        sizeof(arr[0][0]) // 单位:字节

	// 行数:
        row = sizeof(arr)/ sizeof(arr[0])

	// 列数:
        col = sizeof(arr[0])/ sizeof(arr[0][0])

3. 数组的地址:

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

数组的首地址 == 数组的首元素地址 == 数组的首行地址。

4. 二维数组的初始化:

	1)常规初始化:
		int arr[3][5] = {{2, 3, 54, 56, 7 }, {2, 67, 4, 35, 9}, {1, 4, 16, 3, 78}};
	2) 不完全初始化:
		int arr[3][5] = {{2, 3}, {2, 67, 4, }, {1, 4, 16, 78}};  未被初始化的数值为 0 
		int arr[3][5] = {0};	初始化一个 初值全为0的二维数组
		int arr[3][5] = {2, 3, 2, 67, 4, 1, 4, 16, 78};   【少见】 系统自动分配行列。
	3)不完全指定行列初始化:
		int arr[][] = {1, 3, 4, 6, 7};  // 二维数组定义必须指定列值。
	  	int arr[][2] = { 1, 3, 4, 6, 7 };  // 可以不指定行值。

总结一下:初始化必须是清晰的,没有歧义的;初始化了但是没有完全初始化的部分都会变成0;此外初始化一个全零二维数组的做法也需要掌握;

练习:求出5名学生3门功课的总成绩。(一个学生的总成绩。一门功课的总成绩)

// 初始化二维数组的大小
	int scores[5][3];
// 获取行和列的数目
	int row = sizeof(scores) / sizeof(scores[0]);
	int col = sizeof(scores[0]) / sizeof(scores[0][0]);
// 使用循环遍历二维数组
	// 获取 5 名学生、3门功课成绩
	for (size_t i = 0; i < row; i++)
	{
		for (size_t j = 0; j < col; j++)
		{
			scanf("%d", &scores[i][j]);
		}
	}
	// 求一个学生的总成绩
	for (size_t i = 0; i < row; i++) // 每个学
	{
		int sum = 0;
		for (size_t j = 0; j < col; j++)// 每个学生的成绩
		{
			sum += scores[i][j];
		}
		printf("第%d个学生的总成绩为:%d\n", i+1, sum);
	}
	//求一门功课的总成绩
	for (size_t i = 0; i < col; i++)  // 第几门功课
	{
		int sum = 0;
		for (size_t j = 0; j < row; j++)  // 每门功课的第几个学生
		{
			sum += scores[j][i];
		}
		printf("第%d门功课的总成绩为:%d\n", i + 1, sum);
	}

小技巧:快捷导入代码:

VS --> 工具--> 代码片段管理器 --> Visual C++

5. 多维数组:

	// 三维数组:[层][行][列]

	// 数组类型 数组名[层][行][列];

	int arr[3][3][4];

	{ {12, 3, 4, 5}
	  {12, 3, 4, 5} },

	{ {12, 3, 4, 5}
	  {12, 3, 4, 5} },

	{ {12, 3, 4, 5}
	  {12, 3, 4, 5} },

        // 遍历方式
	for(i = 0; i < 3; i++)  层
		for (j = 0; j < 3; j++)  行
			for (k = 0; k<4; k++)  列
				printf("%d ", arr[i][j][k]);

6. 字符数组和字符串区别:

	// 字符数组: 
		char str[5] = {'h', 'e', 'l', 'l', 'o'};	

	// 字符串:
		char str[6] = {'h', 'e', 'l', 'l', 'o', '\0'};

		char str[6] = "hello";

		printf("%s");	使用printf打印字符串的时候,必须碰到 \0 结束。

简单的说就是使用""会自动在末尾补全一个'\0'字符,而使用普通数组的方式需要自己手动补全。

练习:键盘输入字符串,存至str[]中,统计每个字母出现的次数。

	for (size_t i = 0; i < 10; i++)
	{
		scanf("%c", &str[i]);
	}

	int count[26] = {0};  // 代表26个英文字母出现的次数。 

	for (size_t i = 0; i < 11; i++)
	{
		int index = str[i] - 'a';	// 用户输入的字符在 count数组中的下标值。
		count[index]++;
	}

	for (size_t i = 0; i < 26; i++)
	{
		if (count[i] != 0)
		{
			printf("%c字符在字符串中出现 %d 次\n", i+'a', count[i]);
		}
	}

这个使用哈希表做是最合适的,但是上面是使用数组来做的;这里利用到了整数和char类型的可以进行混合运算;需要记住的是,字符'a'的ASCII码为:97,而'A'的为65;

7. 字符串获取(scanf):

    1. 用于存储字符串的空间必须足够大,防止溢出。 char str[5];
    1. 获取字符串,%s, 遇到空格 和 \n 终止。

借助“正则表达式”, 获取带有空格的字符串:scanf("%[^\n]", str);

8. 字符串操作函数:

  • gets: 从键盘获取一个字符串, 返回字符串的首地址。 可以获取带有 空格的字符串。 【不安全】

    char *gets(char *s);

    参数:用来存储字符串的空间地址。

    返回值:返回实际获取到的字符串首地址。

  • fgets: 从stdin获取一个字符串, 预留 \0 的存储空间。空间足够大 \n, 空间不足舍弃 \n 【安全】

    char *fgets(char *s, int size, FILE *stream);

    参1:用来存储字符串的空间地址。

    参2:描述空间的大小。

    参3:读取字符串的位置。 键盘 --> 标准输入:stdin

    返回值:返回实际获取到的字符串首地址。

  • puts:将一个字符串写出到屏幕. printf("%s\n", "hello");和printf("hello\n");和puts("hello"); 作用相同: puts("hello");输出字符串后会自动添加 \n 换行符。

    int puts(const char *s);

    参1:待写出到屏幕的字符串。

    返回值: 成功:非负数 0。 失败: -1.

  • fputs:将一个字符串写出到stdout.输出字符串后, 不添加 \n 换行符。

    int fputs(const char * str, FILE * stream);

    参1:待写出到屏幕的字符串。 屏幕 -->标准输出: stdout

    参数:写出位置 stdout

    返回值: 成功:0。 失败: -1.

  • strlen: 碰到 \0 结束。

    size_t strlen(const char *s);

    参1: 待求长度的字符串

    返回:有效的字符个数。

9. 字符串合并函数:

	char str1[] = "hello";
	char str2[] = "world";

	char str3[100] = {0};

	int i = 0;		// 循环 str1
	while (str1[i] != '\0') // 等价于 while(str1[i]) 等价于 while (str1[i] != 0)
	{
		str3[i] = str1[i];  // 循环着将str1中的每一个元素,交给str3
		i++;
	}					// str3=[h e l l o]
	//printf("%d\n", i);  --> 5

	int j = 0;		// 循环 str2
	while (str2[j])
	{
		str3[i+j] = str2[j];
                j++;
	}					// str3=[h e l l o w o r l d]

	// 手动添加 \0 字符串结束标记
	str3[i + j] = '\0';

10. 函数的作用:

  1. 提高代码的复用率
  2. 提高程序模块化组织性。

11. 函数分类:

  • 系统库函数: 标准C库。 libc
    1. 引入头文件 --- 声明函数
    2. 根据函数原型调用。
  • 用户自定义:除了需要提供函数原型之外,还需要提供函数实现。

12. 随机数:

    1. 播种随机数种子:srand(time(NULL));
    1. 引入头文件 #include <stdlib.h> <time.h>
    1. 生成随机数: rand() % 100; 取余操作称为收紧。

13. 函数定义:

包含:

  • 函数原型(返回值类型、函数名、形参列表)
  • 函数体(大括号一对, 具体代码实现)
  • 形参列表: 形式参数列表。一定包括:类型名 形参名。
	int add(int a, int b, int c)
	{
		return a+b+c;
	}

	int test(char ch, short b, int arr[], int m)

14. 函数调用:

形式为:函数名(实参列表);
int ret = add(10, 4, 28); 实参(实际参数): 在调用时,传参必须严格按照形参填充。(参数的个数、类型、顺序)并且没有类型描述符。

15. 函数声明:

  • 包含: 函数原型(返回值类型、函数名、形参列表) + “;”
  • 要求: 在函数调用之前,编译必须见过函数定义。否则,需要函数声明。 int add(int a, int b, int c)
  • 隐式声明:【不要依赖】
  • 默认编译器做隐式声明函数时,返回都为 int ,根据调用语句不全函数名和形参列表。 #include <xxx.h> --> 包含函数的声明

15. exit函数: #include <stdlib.h>

  • return关键字: 返回当前函数调用,将返回值返回给调用者。
  • exit()函数: 退出当前程序。

16. 多文件编程:

  1. 将多个含有不同函数功能 .c 文件模块,编译到一起,生成一个 .exe文件。
    • <>包裹的头文件为系统库头文件。
    • ""包裹的头文件为用户自定义头文件。

头文件守卫:

防止头文件重复包含

  • windows中: #pragma once

  • 常用做法:

    #ifndef __HEAD_H__		<--- head.h

    #define __HEAD_H__

        .... 头文件内容

    #endif