二维数组、字符串与函数
二维数组
基本使用
二维数组的本质,是"多个一维数组组合而成"。
int arr[10] = {1,2,3,4,5,6,7}; // 一维数组
{1,2,3,4,5,6,7}
{1,2,3,4,5,6,7}
{1,2,3,4,5,6,7} // 多个一维数组,组成二维数组。
定义语法
int arr[行][列] = {数组元素}
int arr[2][3] =
{
{2, 5, 8}, // 第0行
{7, 9, 10} // 第1行
};
// 常规写法:
int arr[3][5] = {{2, 3, 54, 56, 7}, {2, 67, 4, 35, 9}, {1, 4, 9, 3, 78}};
size_t 来源(for 中自动补齐的 size_t): 查看方法1:右键 —— 转到定义;方法2:F12。
typedef unsigned int size_t; // 给 unsigned int 起别名,叫 size_t
打印
// 以下是打印 2 维数组的方法:
int arr[3][5] = { {2, 3, 54, 56, 7}, {2, 67, 4, 35, 9}, {1, 4, 9, 3, 78} };
for (size_t i = 0; i < 3; i++) // 行
{
for (size_t j = 0; j < 5; j++) // 列
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
特性
包括:数组大小、一行大小、一个元素大小、行数、列数、地址合一。
printf("数组大小:%u\n", sizeof(arr));
printf("数组一行大小:%u\n", sizeof(arr[0]));
printf("数组一个元素大小:%u\n", sizeof(arr[0][0]));
int row = sizeof(arr) / sizeof(arr[0]); // 数组总大小 / 每行大小
int col = sizeof(arr[0]) / sizeof(arr[0][0]); // 一行大小 / 每个元素大小
地址合一:数组的地址 == 数组的首元素地址 == 数组的首行地址。
printf("%p\n", arr); // 数组的首地址
printf("%p\n", arr[0]); // 数组首行地址
printf("%p\n", &arr[0][0]); // 数组首元素的地址
初始化
常规初始化
int arr[3][5] = { {2, 3, 54, 56, 7}, {2, 67, 4, 35, 9}, {1, 4, 9, 3, 78} };
不完全初始化
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, 4, 5, 6, 7, 8, 9, 99, 2, 16, 78}; //【少见】系统自动分配行列
不完全指定行列初始化
int arr[][] = {1, 23, 4, 56, 7, 8}; 【错误】 // 二维数组定义,至少需要指定 列值。
int arr[][2] = { 1, 23, 4, 56, 7, 8, 10}; // 可以不指定行值。
int row = sizeof(arr) / sizeof(arr[0]);
int col = sizeof(arr[0]) / sizeof(arr[0][0]);
for (size_t i = 0; i < row; i++) // 行
{
for (size_t j = 0; j < col; j++) // 列
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
练习:求 5 名学生 3 门功课的总成绩
求出 5 名学生 3 门功课的总成绩。(总成绩:一个学生的总成绩;一门功课的总成绩)
int main(void)
{
int scores[5][3]; // 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 < col; i++) // 一次取出,每个学生的 一门功课
{
int sum = 0; // 累加每门功课的分数。
for (size_t j = 0; j < row; j++) // 每门功课第几个学生
{
sum += scores[j][i];
}
printf("第%d门功课总成绩:%d\n", i + 1, sum);
}
// 求每个学生的总成绩
for (size_t i = 0; i < row; i++) // 每个学生
{
int sum = 0; // 累加每个学生的成绩。
for (size_t j = 0; j < col; j++) // 每个学生的成绩
{
sum += scores[i][j]; // sum = sum + scores[i][j];
}
printf("第%d个学生的总成绩为:%d\n", i+1, sum);
}
system("pause");
return EXIT_SUCCESS;
}
多维数组(了解)
三维数组:[层][行][列]。
语法:类型名 数组名[层][行][列]。
int arr[3][3][4] =
{
{
{{12, 3, 4, 5}}, // 第0行
{{12, 3, 4, 5}}, // 第1行
{{12, 3, 4, 5}} // 第2行
}, // 第0层
{
{}, // 第0行
{}, // 第1行
{} // 第2行
} // 第1层
};
打印:
int main(void)
{
int arr[3][4][2] =
{
{
{1, 2},
{3, 4},
{5, 6},
{7, 8}
},
{
{12, 24},
{31, 49},
{5, 46},
{17, 88}
},
{
{122, 24},
{311, 419},
{15, 46},
{17, 188}
}
};
for (size_t i = 0; i < 3; i++) // 层
{
for (size_t j = 0; j < 4; j++) // 行
{
for (size_t k = 0; k < 2; k++) // 列
{
printf("%d ", arr[i][j][k]);
}
printf("\n");
}
printf("\n\n");
}
system("pause");
return EXIT_SUCCESS;
}
层数可以省略:
int arr[2][3][4] = {1,2,3,4,5,6,7,8,9};
int arr[][3][4] = {1,2,3,4,5,6,7,8,9}; // 层数,可以省略。
数组首地址 == 首层地址 == 首层首行地址 == 首元素地址。
4维、5维、6维……N维同理:
int arr[2][3]
short arr[2][3]
float arr[2]
long long arr[2][3][5]
字符串
一串字符。C 语言中,一定使用 '\0' 结束。
字符数组和字符串的区别
char str1[5] = {'h','e','l','l','o'}; // 不是字符串,没有 \0
char str2[6] = {'h','e','l','l','o','\0'}; //【麻烦】
char str3[6] = "hello"; // 自动带有 \0 结束标志。
'a' != "a"("a"实际是'a','\0')'abc'是一个错误定义!既不是字符串,也不是有效字符。
字符串输出
printf("%s"):打印字符串,挨着从字符串的第一个字符顺序向后打印,打印到 '\0' 结束。没碰到 '\0' 不结束。
其他格式匹配符
| 格式符 | 说明 |
|---|---|
%Ns | 显示 N 个字符的字符串,不足 N 用空格向右填充 |
%0Ns | 显示 N 个字符的字符串,不足 N 用 0 向左填充 |
%-Ns | 显示 N 个字符的字符串,不足 N 用空格向左填充 |
%% | 与字符串无直接关系,显示一个 %;转义 % 使用 %% 本身 |
printf("|%9s|\n", str);
printf("|%09s|\n", str);
printf("|%-9s|\n", str);
// 输出【10 % 3 = 1】:
printf("10 %% 3 = 1\n");
练习:统计每个字母出现的次数
键盘输入字符串,存至 str[] 中,统计每个字母出现的次数。
int main(void)
{
char str[1024] = { 0 }; // 定义字符数组,初值均为0
for (size_t i = 0; i < 11; i++)
{
scanf("%c", &str[i]); // helloworld
}
// 定义一个有26个元素的数组,初始化成 0
char count[26] = { 0 }; // 记录26个英文字符出现的次数。
for (size_t i = 0; i < 11; i++)
{
int index = str[i] - 'a'; // 提取每个字符,在 count 表中对应的下标。
count[index]++; // 对应字符位置++,代表该字符出现了一次。
}
// 循环遍历 count 数组,打印出每个字符出现的次数。
for (size_t i = 0; i < 26; i++)
{
if (count[i] != 0) // 0 == \0
{
printf("%c字符,在字符串%s中,出现了%d次\n", i+'a', str, count[i]);
}
}
system("pause");
return EXIT_SUCCESS;
}
scanf 获取字符串
注意事项:
- 用于存储字符串的空间,必须足够大!防止溢出。
%s遇到 空格 和\n终止。- 借助"正则表达式",可以获取带有空格的字符串:
scanf("%[^\n]", str);
char str[1024] = {0}; // 定义字符串存储的空间,保证足够大。
scanf("%s", str);
char str[1024] = {0};
scanf("%s", str); // 获取 "hello world haha xixi" 字符串
printf("%s", str); // 输出 hello
字符串操作函数
gets
从(键盘)标准输入 stdin 获取字符串。返回字符串首地址,可以获取带有空格的字符串,不保存 \n,将其替换为 \0。
#include <stdio.h>
char *gets(char *s); // char * 等价于 char []
// 参数:用来存储字符串的空间地址。
// 返回值:返回实际获取到的字符串的首地址。
// 示例
char str[1024] = {0};
printf("获取的字符串为:%s\n", gets(str));
fgets
从(键盘)标准输入 stdin 获取字符串。一定会给字符串预留 \0 空间,可以获取带有空格的字符串。如果空间足够,保留 \n;如果空间不足,不保留 \n。
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream); // char * 等价于 char []
// 参1:用来存储字符串的空间地址。
// 参2:空间的大小。(严格对应实际空间的大小)
// 参3:读取字符串的位置。 —— stdin(键盘)
// 返回值:返回实际获取到的字符串的首地址。
// 示例:
char str[15];
int len = sizeof(str);
printf("获取到的字符串为:%s\n", fgets(str, len, stdin));
puts
将字符串输出到 屏幕 标准输出 stdout。输出后会自动向屏幕输出 \n。
// printf("%s\n", "hello")
#include <stdio.h>
int puts(const char *s); // char * 等价于 char []
// 参:待写入到屏幕的字符串。
// 返回值:成功:0, 失败:-1
// 示例:
char str[] = "hello world";
int ret = puts(str);
printf("ret = %d\n", ret);
fputs
将字符串输出到 屏幕 标准输出 stdout。不自动添加 \n 字符。
#include <stdio.h>
int fputs(const char * str, FILE * stream); // char * 等价于 char []
// 参1:待写出到屏幕的字符串。
// 参2:写出的位置。—— stdout 标准输出(屏幕)。
// 返回值:成功:0, 失败:-1
// 示例:
char str[] = "hello world\n";
int ret = fputs(str, stdout);
printf("ret = %d\n", ret);
strlen
作用:获取一个字符串有效字符个数(字符串的长度)。不含 \0(碰到 \0 结束)。
#include <string.h>
size_t strlen(const char *s);
// 参:待求长度的字符串
// 返回:有效字符个数
// 示例:
char str[] = "hello world\n";
printf("有效长度=%u\n", strlen(str)); // 不含有 \0 长度
printf("sizeof=%u\n", sizeof(str)); // 含有 \0 长度
实现 strlen 函数:
int main(void)
{
char str[] = "hello world";
int i = 0;
while (str[i] != '\0')
{
i++;
}
printf("不含\\0的字符串长度为:%d\n", i);
printf("strlen = %d\n", strlen(str));
system("pause");
return EXIT_SUCCESS;
}
字符串追加
int main(void)
{
char str1[] = "hello";
char str2[] = "world";
char str3[100];
// 循环将 str1 中的字符,依次写入到 str3 中
int i = 0;
while (str1[i] != '\0')
{
str3[i] = str1[i];
i++;
} // 循环结束,str3 =【hello】无 \0
// printf("i = %d\n", i); --- 循环结束为 5 。
int j = 0; // 循环 str2
// 循环将 str2 中的字符,接着 str1 的内容顺序写入到 str3 中
while (str2[j]) // while(str2[j] != 0) == while (str2[j] != '\0')
{
str3[i+j] = str2[j];
j++;
} // 循环结束,str3 =【helloworld】无 \0
// 手动添加 \0 结束标记
str3[i + j] = '\0';
printf("str3 = %s\n", str3);
system("pause");
return EXIT_SUCCESS;
}
函数
函数的作用
- 提高代码复用率。
- 提高程序模块化组织性。
函数分类
系统库函数:标准 C 库 libc。
- 必须要引入头文件
#include <xxx.h>—— 函数声明。 - 根据库函数原型,调用函数。
用户自定义函数:如 bubble_sort()、myPrint()。除了需要提供函数原型之外,还需要提供函数实现。
使用函数
函数定义、函数声明、函数调用。
函数定义
函数定义必须包含"函数原型"和"函数体"。
- 函数原型:返回值类型 + 函数名 + 形参列表。
- 形参列表:形式参数列表,一定包含类型名、形参名。
- 函数体:一对
{}包裹函数实现。
// 加法函数
int add(int a, int b)
// 例子:
int add(int a, int b)
{
int ret = a + b;
return ret;
}
// int test(char ch, short b, int arr[], int m)
函数调用
包含:函数名(实参列表);
- 实参(实际参数):在调用时,传参必须严格按照形参填充(参数个数、类型、顺序)。
- 实参在调用时,没有类型描述符。
// 例子
int m = 10;
int n = 20;
int ret = add(m, n);
函数声明
包含:函数原型(返回值类型 + 函数名 + 形参列表)+ ;。
- 要求在函数调用之前,编译器必须见过函数定义,否则需要函数声明。
- 如果没有函数声明,编译默认做"隐式声明"。
隐式声明【不要依赖】:
- 编译认为所有的函数,返回值都是
int。 - 可以根据函数调用,推测函数原型。
#include <xxx.h>内部,包含函数声明。
// 例子
int add(int a, int b);
exit 函数
📷(此处原文为配图,略)
(本节内容在下一章"07-高级语法(指针)"开头继续展开。)
多文件编程
📷(此处原文为配图,略)
(本节内容在下一章"07-高级语法(指针)"开头继续展开。)