写在前面:
- 本系列专栏主要介绍C语言的相关知识,思路以下面的参考链接教程为主,大部分笔记也出自该教程。
- 除了参考下面的链接教程以外,笔者还参考了其它的一些C语言教材,笔者认为重要的部分大多都会用粗体标注(未被标注出的部分可能全是重点,可根据相关部分的示例代码量和注释量判断,或者根据实际经验判断)。
- 如有错漏欢迎指出。
参考教程:C语言程序设计从入门到进阶【比特鹏哥c语言2024完整版视频教程】(c语言基础入门c语言软件安装C语言指针c语言考研C语言专升本C语言期末计算机二级C语言c语言_哔哩哔哩_bilibili
一、数组概述
(1)所谓数组,就是一个集合,里面按顺序存放了相同类型的数据元素。
(2)数组中的每个数据元素都是相同的数据类型。
(3)数组是由连续的内存位置组成的。
二、一维数组
1、一维数组的定义
一维数组是由具有一个下标的数组元素组成的数组,其定义形式如下:
[];
(1)数据类型是类型说明符,数组名是数组的名字,数组长度是任一值为正整数的int型常量表达式,用来指定数组中元素的个数,即数组的大小。
(2)数组元素的下标是从0到(数组长度-1)。
(3)数组名的命名规范与变量名命名规范一致,不要和变量重名。
#define _CRT_SECURE_NO_WARNINGS 1#include int main(){//代码1int arr1[10];//arr1的类型:int [10]//代码2int count = 10;//int arr2[count];//代码3char arr3[10];//arr3的类型:char [10]float arr4[1];//arr4的类型:float [10]double arr5[20];//arr5的类型:double [10]return 0;}
2、一维数组的初始化
与所有的基本数据类型相同,数组也可以在定义时初始化,有两种形式:
(1)指定数组长度,花括号中各项数据之间以逗号分隔,若花括号内的元素个数小于(只能小于等于,如果大于将会报错)数组长度,则剩下的数组元素默认为零(或者说空)。
[ ] = { , …};
(2)不指定数组长度,花括号中各项数据之间以逗号分隔,数组长度即花括号内的元素个数。
[ ] = { , …};
#define _CRT_SECURE_NO_WARNINGS 1#include int main(){int arr1[10] = { 1,2,3 };//10个元素int arr2[] = { 1,2,3,4 };//4个元素int arr3[5] = { 1,2,3,4,5 }; //5个元素char arr4[3] = { 'a',98, 'c' }; //3个元素char arr5[] = { 'a','b','c' };//3个元素char arr6[] = "abcdef";//7个元素,还有一个'\0'在最后面return 0;}
3、访问数组元素的语法形式
[]
(1)表达式是非负的int型表达式,称为下标,下标用于指定所要访问的数组中元素的位置,在C++中“[]”是一个运算符,称为下标运算符。
(2)数组下标从0开始,长度为n的数组,其下标的范围是0到n-1。(下标超出范围会造成越界访问,不管会不会报错,程序都是会存在问题的)
数组元素 | 数组a第1个元素 | 数组a第2个元素 | 数组a第3个元素 | … | 数组a第n个元素 |
访问方式 | a[0] | a[1] | a[2] | … | a[n-1] |
元素值 | … |
(3)在数组定义以后,给数组元素赋值时,必须一个元素一个元素地逐个访问。
(4)数组名是常量,不可以对其进行赋值,但可对其元素进行赋值。
#define _CRT_SECURE_NO_WARNINGS 1#include int main(){int arr[10] = { 0 }; //数组的不完全初始化//计算数组的元素个数int sz = sizeof(arr) / sizeof(arr[0]);//对数组内容赋值,数组是使用下标来访问的,下标从0开始int i = 0;//i做下标for (i = 0; i < 10; i++){arr[i] = i;}//输出数组的内容for (i = 0; i < 10; ++i){printf("%d ", arr[i]);}return 0;}
4、一维数组在内存中的存储
一维数组在内存中是连续存放的,随着下标的增加,地址由低到高。
#define _CRT_SECURE_NO_WARNINGS 1#include int main(){int arr[10] = { 0 };int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 0; i < sz; ++i){printf("&arr[%d] = %p\n", i, &arr[i]);//数组在内存中是连续存放的,随着下标的增加,地址由低到高}return 0;}
二、二维数组
1、二维数组的定义
二维数组是以行和列(即二维)形式排列的固定数目元素的集合,并且组成数组的每个元素的类型都相同,即带有两个下标的数组。定义二维数组的语法是:
[][];
(1)数据类型是类型说明符,数组名是数组的名字,数组名的命名规范与变量名命名规范一致,不要和变量重名。
(2)两个表达式都是值为正整数的常量表达式,分别用来指定数组中行和列的数目。
#define _CRT_SECURE_NO_WARNINGS 1#include int main(){//数组创建int arr[3][4]; //相当于创建一个3行4列的矩阵char arr[3][5];double arr[2][4];return 0;}
2、二维数组的初始化
与一维数组相同,二维数组也可以在定义时初始化,有三种形式:
(1)指定数组的行数和列数,在同一行中的元素可以用花括号括起来,并用逗号分隔,这种方式最为直观,推荐使用。
[ ][ ] = { {, } , {, } };
(2)指定数组的行数和列数,初始化列表中的数据按顺序从二维数组的第一行第一列开始逐行赋值。
[ ][ ] = { , , , };
(3)仅指定数组的列数(无论如何列数都不能省略),程序会根据初始化列表的元素个数推断出二维数组的行数,然后从二维数组的第一行第一列开始逐行赋值。
[ ][ ] = { , , , };
#define _CRT_SECURE_NO_WARNINGS 1#include int main(){//数组初始化int arr[3][4] = { 1,2,3,4 };int arr[3][4] = { {1,2},{4,5} };//二维数组可理解为一维数组的数组int arr[][4] = { {2,3},{4,5} }; //二维数组如果有初始化,行数可以省略,列数不能省略return 0;}
3、访问数组元素的语法形式
[][]
(1)两个表达式是非负整数的表达式。
(2)表达式1指定行下标,表达式2指定列下标。
(3)二维数组可以看做一个元素类型为一维数组的一维数组,有r个元素(r为行数),其中每个一维数组的长度均为l(l为列数),一维数组下标从0开始,其下标的范围是0到l-1。(对于二维数组a[r][l],“a[x]”的语意是把二维数组的第x+1行元素当作一维数组,“a[x]”可视作该一维数组的名称)
一维数组a[0] | a[0][0] = | a[0][1] = | … | a[0][l-1] = |
一维数组a[1] | a[1][0] = | a[1][1] = | … | a[1][l-1] = |
一维数组a[2] | a[2][0] = | a[2][1] = | … | a[2][l-1] = |
… | … | … | … | … |
一维数组a[r-1] | a[r-1][0] = | a[r-1][1] = | … | a[r-1][l-1] = |
#define _CRT_SECURE_NO_WARNINGS 1#include int main(){int arr[3][4] = { 0 };int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 4; j++){arr[i][j] = i * 4 + j;}}for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 4; j++){printf("%d ", arr[i][j]);}}return 0;}
4、二维数组在内存中的存储
二维数组在内存中也是连续存放的,从第一行第一列开始,随着下标的增加,地址由低到高,第一行的元素完毕后,转向第二行第一列的元素,以此类推。
#define _CRT_SECURE_NO_WARNINGS 1#include int main(){int arr[3][4];int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 4; j++){printf("&arr[%d][%d] = %p\n", i, j, &arr[i][j]);//二维数组在内存中也是连续存储的}}return 0;}
三、字符数组
1、char类型的一维数组
(1)char类型的一维数组的存储结构如下所示(实际存储的是字符对应的ASCII码):
a[0] | a[1] | a[2] | a[3] | a[4] | a[5] | … | a[n-1] |
‘字符1’ | ‘字符2’ | ‘字符3’ | ‘字符4’ | ‘字符5’ | ‘字符6’ | … | ‘字符n-1’ |
(2)字符串以空字符结尾(系统在处理字符串常量存储时会自动加一个’\0’),而字符数组可以不含空字符。
(3)字符串可以存储在一维字符数组内,比如:
char word[10] = “Hello”; //存储该字符串至少需要6个char存储空间,因为要算上’\0’
(4)由双引号括起来的字符串常量具有静态字符串数组类型,换句话说,字符串常量也可视作一个字符数组。
#define _CRT_SECURE_NO_WARNINGS 1#include int main(){//使用字符串初始化字符数组char str[] = "Hello World";//字符串名后面要加中括号[];等号后面要用双引号包含字符串//按一般方式初始化字符数组char char_group[] = { 'H','e','l','l','o',' ','W','o','r','l','d','\0','1' };//'\0'是字符串结束标志//以字符串形式输出(遇到'\0'即认为字符串结束,将会停止输出该字符串)printf("%s\n", str);printf("%s\n", char_group);return 0;}
2、字符数组的输入输出
(1)字符数组的输入输出可以有两种方法:
①借助循环用格式符“%c”将逐个字符输入输出。
②用格式符“%s”将整个字符串一次输入或输出,输入时遇到空格表示输入结束,输出时遇结束符’\0’就停止输出。(格式符“%s”需对应字符数组的首元素的地址)
(2)举例:
#define _CRT_SECURE_NO_WARNINGS 1#include #include int main(){//将整个字符串一口气输入char str[100]; //预留足够的空间存储输入的字符串scanf("%s", str);//str是字符数组首元素的地址,不用加“&”,往后会详细解释//字符逐一输入char char_group[100];//预留足够的空间存储输入的字符串int i = 0;while (i < 100){//scanf("%c", &(char_group[i])); 这种方式显然很不合适!i++;}//以字符串形式输出(遇到'\0'即认为字符串结束,将会停止输出该字符串)printf("%s\n", str);//printf("%s\n", char_group);//字符逐一输出i = 0;while (i < 100){//printf("%c", str[i]);//printf("%c", char_group[i]); 这种方式显然很浪费算力!i++;}return 0;}
3、字符串处理函数
(1)使用字符串处理函数需要添加头文件string.h。
(2)puts函数——输出字符串的函数:
①作用:将一个字符串输出到终端,以’\0’为结束标志。
②示例:
#define _CRT_SECURE_NO_WARNINGS 1#include int main(){char str[] = "114514";puts(str);return 0;}
(3)gets函数——输入字符串的函数:
①作用:从终端输入一个字符串到字符数组,并且得到一个函数值,这个函数值是字符数组的起始地址。
②示例:
#define _CRT_SECURE_NO_WARNINGS 1#include #include int main(){char str[100];gets(str);puts(str);return 0;}
(4)strcat函数——字符串连接函数:
①作用:参数为两个字符数组首元素的地址,把第二个字符数组接在第一个字符数组后面(第一个字符串中的终止空字符被第二个字符串的第一个字符覆盖),结果放在第一个字符数组中,并返回第一个字符数组的地址。(需要注意的是,第一个字符数组的空间需要足够大,且不能有const修饰,另外字符串不可以自己给自己追加,在追加过程中原本自身的’\0’会被覆盖掉,从而陷入死循环)
②示例:
#define _CRT_SECURE_NO_WARNINGS 1#include #include int main(){char str1[10] = "Hello";char str2[10] = "鼠标";printf("%s\n", strcat(str1, str2));printf("%s\n", str1);return 0;}
(5)strncat函数——字符串连接函数:
①作用:参数为两个字符数组首元素的地址和指定追加字符的个数,将第二个字符串的前几个字符追加到第一个字符串后面(第一个字符串末尾的‘\0’被覆盖),再加上一个空字符,如果第二个字符串的长度小于指定个数,则只复制到终止空字符之前的内容。
②示例:
#define _CRT_SECURE_NO_WARNINGS 1#include #include int main(){char str1[20] = "abcdef";char str2[10] = "bbb";strncat(str1, str2, 1);printf("%s\n", str1);return 0;}
(6)strcpy和strncpy函数——字符串复制函数:
①作用:参数为两个字符数组首元素的地址,将第二个字符串复制到第一个字符串中(第一个字符数组的容量必须足够大,至少不应小于第二个字符数组,且第一个参数必须写成数组名形式,不能有const修饰,第二个参数可以是数组名也可以是一个字符串常量)。
②示例:
#define _CRT_SECURE_NO_WARNINGS 1#include #include int main(){char str1[10] = "Hello";char str2[10] = "鼠标";strcpy(str1, str2);printf("%s\n", str1);return 0;}
(7)strcmp——字符串比较函数:
①作用:参数为两个字符数组首元素的地址,如两个数组全部字符相同,则认为两个字符串相同,返回0;如两个数组出现不同字符,则以第一对不相同的字符的比较结果为准,至于比较规则个人认为意义不大,如果结果为第一个字符串大于第二个字符串则返回一个正整数,果结果为第一个字符串小于第二个字符串则返回一个负整数。
②示例:
#define _CRT_SECURE_NO_WARNINGS 1#include #include int main(){char str1[10] = "Hello";char str2[10] = "hello";char str3[10] = "HEllo";printf("%d\n", strcmp(str1, str2));printf("%d\n", strcmp(str1, str3));printf("%d\n", strcmp(str2, str3));return 0;}
(8)strlen函数——测字符串长度的函数:
①作用:返回一个字符串的实际长度,以’\0’作为结束标志,但结束标志不算在字符串的长度内。
②示例:
#define _CRT_SECURE_NO_WARNINGS 1#include#includeint main(){"acdfdsg";""; //空字符串char arr1[] = "abc";printf("%s\n", arr1);char arr2[] = { 'a','b','c' };printf("%s\n", arr2);char arr3[] = { 'a','b','c','\0' }; printf("%s\n", arr3);printf("arr1字符串的长度为:%d\n", strlen(arr1));printf("arr2字符串的长度为:%d\n", strlen(arr2)); //“\0”的位置不清楚,字符串不知何时结束,长度为随机值printf("arr3字符串的长度为:%d\n", strlen(arr3));return 0;}
(9)strlwr函数——转换为小写的函数:
①作用:将一个字符串中的大写字母转换为小写字母。
②示例:
#define _CRT_SECURE_NO_WARNINGS 1#include #include int main(){char str1[10] = "Hello";strlwr(str1);printf("%s\n", str1);return 0;}
(10)strupr函数——转换为大写的函数:
①作用:将一个字符串中的小写字母转换为大写字母。
②示例:
#define _CRT_SECURE_NO_WARNINGS 1#include #include int main(){char str1[10] = "Hello";strupr(str1);printf("%s\n", str1);return 0;}
四、越界访问与数组名的作用
1、越界访问
以一维数组为例,数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1,数组的下标如果小于0,或者大于n-1,就是数组越界访问,超出了数组合法空间的访问。(二维数组的行和列也可能存在越界,多维数组同理)
int main(){int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int i = 0;for (i = 0; i <= 10; i++){printf("%d\n", arr[i]);//当i等于10的时候,越界访问了}return 0;}
2、数组名的作用
(1)除以下两种情况外,所有的数组名都表示数组首元素的地址:
①sizeof(数组名)——计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。
②&数组名——取出的是数组的地址(数组的地址和数组首元素地址相同,但是二者并不等价,这点在指针运算中会有所体现)。
(2)二维数组视为一维数组的数组,那么二维数组的数组名表示的首元素的地址其实是一个一维数组的地址(第一行的地址)。
(3)数组作为函数的参数时,实际传递的就是数组首元素的地址。(关于函数在下一章会详细介绍)
#define _CRT_SECURE_NO_WARNINGS 1#include void bubble_sort(int arr[] ,int len){int i = 0;for (i = 0; i < len - 1; i++){int j = 0;for (j = 0; j arr[j + 1]){int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}}int main(){int arr[] = { 3,1,7,5,8,9,0,2,4,6 }; //需要使用冒泡排序将其变为有序数组int len = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, len); //数组名的本质是数组首元素的地址,形参中的arr本质上其实是指针变量for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%d ", arr[i]);}return 0;}
五、应用数组设计的两个小游戏
1、三子棋人机对战
(1)main.cpp:
#include"game.h"void menu(){printf("***********************************\n");printf("***** 1. 开始游戏0.退出游戏 *****\n");printf("***********************************\n");printf("请输入1或0进行选择:\n");}void game(){system("cls");char board[ROW][COL] = { 0 };InitBoard(board, ROW, COL);bool ret = false;while (!ret){PrintBoard(board, ROW, COL);PlayerMove(board, ROW, COL);PrintBoard(board, ROW, COL);ret = Determine(board, ROW, COL);if (ret)break;ComputerMove(board, ROW, COL);PrintBoard(board, ROW, COL);ret = Determine(board, ROW, COL);if (ret)break;system("pause");system("cls");}system("pause");system("cls");}int main(){srand((unsigned)time(NULL));int choice ;do{menu();scanf("%d", &choice);switch (choice){case 1://开始游戏game();break;case 0://退出游戏break;default:{printf("输入有误,请重新输入!\n");system("pause");system("cls");break;} }} while (choice);system("pause");return 0;}
(2)game.h:
#pragma once#define _CRT_SECURE_NO_WARNINGS 1#define ROW 3#define COL 3#include#include#includevoid InitBoard(char board[ROW][COL], int row, int col);//初始化棋盘void PrintBoard(char board[ROW][COL], int row, int col); //打印棋盘void PlayerMove(char board[ROW][COL], int row, int col); //玩家下棋void ComputerMove(char board[ROW][COL], int row, int col); //电脑下棋bool Determine(char board[ROW][COL], int row, int col);//判断输赢
(3)game.cpp:
#include"game.h"void InitBoard(char board[ROW][COL], int row, int col){for (int i = 0; i < row; i++){for (int j = 0; j < col; j++){board[i][j] = ' ';}}}void PrintBoard(char board[ROW][COL], int row, int col){int i = 0, j = 0;for (i = 0; i < row; i++){for (j = 0; j < col; j++){if (j == col - 1){printf(" %c \n", board[i][j]);break;}printf(" %c |", board[i][j]);}if (i == row - 1){break;}for (j = 0; j < col; j++){if (j == col - 1){printf("---\n");break;}printf("---|");}}}void PlayerMove(char board[ROW][COL], int row, int col){int x = 0, y = 0;printf("请玩家输入坐标:");do{scanf("%d %d", &x, &y);if ((x<1 || yrow || y>col) || (board[x - 1][y - 1] != ' ')){printf("输入无效坐标,请重新输入:");}else{break;}} while (true);board[x - 1][y - 1] = '*';printf("玩家下棋:\n");}void ComputerMove(char board[ROW][COL], int row, int col){int x = 0, y = 0;do{x = rand() % row + 1;//非智能随机生成y = rand() % col + 1;if (board[x - 1][y - 1] == ' '){board[x - 1][y - 1] = '#';break;}} while (true);printf("电脑下棋:\n");}bool Determine(char board[ROW][COL], int row, int col){int i = 0, j = 0, a = 0, b = 0, c = 0;//玩家赢for (i = 1; i < row; i++){for (j = 1; j < col; j++){if (board[i][j] == '*'&&board[i - 1][j - 1] == '*')//斜线判断{a++;}if (j == col - 1){if (a == col - 1){printf("玩家胜利,本局游戏结束!\n");return true;}else{a = 0;}}}}for (i = 0; i < row; i++){for (j = 1; j < col; j++){if (board[i][j] == '*'&&board[i][j - 1] == '*')//横线判断{b++;}if (j == col - 1){if (b == col - 1){printf("玩家胜利,本局游戏结束!\n");return true;}else{b = 0;}}}}for (j = 0; j < col; j++){for (i = 1; i < row; i++){if (board[i][j] == '*'&&board[i - 1][j] == '*')//竖线判断{c++;}if (i == row - 1){if (c == row - 1){printf("玩家胜利,本局游戏结束!\n");return true;}else{c = 0;}}}}//电脑赢for (i = 1; i < row; i++){for (j = 1; j < col; j++){if (board[i][j] == '#'&&board[i - 1][j - 1] == '#')//斜线判断{a++;}if (j == col - 1){if (a == col - 1){printf("电脑胜利,本局游戏结束!\n");return true;}else{a = 0;}}}}for (i = 0; i < row; i++){for (j = 1; j < col; j++){if (board[i][j] == '#'&&board[i][j - 1] == '#')//横线判断{b++;}if (j == col - 1){if (b == col - 1){printf("电脑胜利,本局游戏结束!\n");return true;}else{b = 0;}}}}for (j = 0; j < col; j++){for (i = 1; i < row; i++){if (board[i][j] == '#'&&board[i - 1][j] == '#')//竖线判断{c++;}if (i == row - 1){if (c == row - 1){printf("电脑胜利,本局游戏结束!\n");return true;}else{c = 0;}}}}//和棋for (i = 0; i < row; i++){for (j = 0; j < col; j++){if (board[i][j] == ' ')goto FLAG;//跳出两层循环}}FLAG:if (i == row && j == col){printf("和棋,本局游戏结束!\n");return true;}return false; //游戏不结束则继续进行}
2、扫雷
(1)main.cpp:
#include "game.h"void menu(){printf("************************************\n");printf("******* 1.开始游戏 0.退出游戏 ******\n");printf("************************************\n");}void game(){char mine[ROWS][COLS] = { 0 }; //存放布雷情况char show[ROWS][COLS] = { 0 }; //存放排雷情况InitBoard(mine, ROWS, COLS, '0'); //在没有布雷的时候,全部格子视为0InitBoard(show, ROWS, COLS, '*'); //没有排雷的格子全部标为*SetMine(mine, ROW, COL);//随机生成雷while (true){system("cls");printf("当前情况:\n");DisplayBoard(mine, ROW, COL);//用于测试printf("\n");DisplayBoard(show, ROW, COL);bool ret = FineMine(mine, show, ROW, COL);if (ret){break;}int count = 0;for (int i = 1; i <= ROW; i++){for (int j = 1; j <= COL; j++){if (show[i][j] == '*')count++;}}if (count == EASY_COUNT){printf("成功找出所有雷!游戏结束!\n");break;}}//该参考没有添加标记地雷功能}int main(){srand((unsigned)time(NULL));int choice;do{system("cls");menu();printf("请输入1或0进行选择:");scanf("%d", &choice);switch (choice){case 1:game();break;case 0:break;default:printf("输入有误,请重新输入!");break;}system("pause");} while (choice);system("pause");return 0;}
(2)game.h:
#pragma once#define _CRT_SECURE_NO_WARNINGS 1#define ROW 9#define COL 9#define ROWS ROW+2#define COLS COL+2#define EASY_COUNT 10#include #include #include void InitBoard(char board[ROWS][COLS], int rows, int cols, char c);void DisplayBoard(char board[ROWS][COLS], int row, int col);void SetMine(char board[ROWS][COLS], int row, int col);bool FineMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);void AddMine(char mine[ROWS][COLS], char show[ROWS][COLS], int rows, int cols);void AddMine1(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);void AddMine2(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);void AddMine3(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);void AddMine4(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);
(3)game.cpp:
#include "game.h"void InitBoard(char board[ROWS][COLS], int rows, int cols, char c){for (int i = 0; i < rows; i++){for (int j = 0; j < cols; j++){board[i][j] = c;}}}void DisplayBoard(char board[ROWS][COLS], int row, int col){for (int i = 1; i <= row; i++){for (int j = 1; j <= col; j++){printf("%c ", board[i][j]);}printf("\n");}}void SetMine(char board[ROWS][COLS], int row, int col){int x, y, count = 0;while (count = 1 && x = 1 && y <= col){if (mine[x][y] == '1'){system("cls");for (int i = 1; i <= row; i++){for (int j = 1; j = 1 && x = 1 && y = 1 && x = 1 && y = 1 && x = 1 && y = 1 && x = 1 && y = 1 && x = 1 && y <= COL){AddMine4(mine, show, x, y + 1);}show[x][y] = deter;}