C语言学习笔记09-数组、字符数组、字符串数组、二维数组(单字符输入输出putchar、getchar,字符串输入输出的scanf、gets、puts)

C语言数组

  数组作用:可以用来保存很多记录(可以看成一种大容器)。一些简单游戏也基本由数组实现,如游戏地图(二维数组)等等。
  一个数组 划分 多个单元(下标区分) -存放-> 多个同类元素

1. 数组定义与初始化

1.1. 定义: <类型> 变量名称[元素数量];
在main函数外先定义全局数组变量,如: int a[10005]; 默认初始化为0
注意点:
(1)元素数量必须是整数,C99之前元素数量必须是编译时刻确定的字面量!而C99这里可以支持变量。MSVC编译器不完全支持C99,VS2022用cpp才能这样写,而且还是不支持scanf入一个变量作为数组长度!
在这里插入图片描述
在这里插入图片描述

(EOF参见C语言学习笔记06结尾部分。)EOF -1 ctrl+Z
不过,DevCpp在使用变量定义数组时也不支持同时做初始化操作。

在这里插入图片描述

(2)数组一旦创建不能改变大小(C99变量被赋值后创建的数组大小也是根据变量值确定的),内部元素的内存排列是依次连续的,元素类型都一样(与数组类型一致)。
(3)arr[10]的下标是0~9(arr[0] ~ arr[9],一个数组单元就是一个变量)。【C和C-like语言的一大特点,从0开始数】需要注意下标的有效范围(遍历时让循环变量i从0到<数组长度,这样循环体内i最大时是数组最大有效下标),不要越界segmentation fault 段错误(概率导致程序bug,指针有关)。

1.2. 初始化:

DevCpp中,static修饰或作为全局变量时的数组会被默认初始化为0,在main中定义时则默认随机值。

(1)利用for循环对数组初始化。
int a[10];
for (int i = 0; i < 10; i++) {
a[i] = 0;
}
上面的也等价于:
int a[10] = {0};

(2)集成初始化-直接依次赋值。
int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, }; //数组共10个元素,最后有无‘,’均可。
int a[10] = {3}; //数组共10个元素,第一个是3,后面全是0
C99标准还支持给数组指定位置赋值:
int a[13] = {[1] = 2, 4, [5] = 6}; //没有赋值的地方取0,值4赋给下标为2的数组单元。
即 0 2 4 0 0 6 0 0 0 0 0 0 0 。
int a[] = {[5] = 6}; //因涉及到最大下标5,所以共6个元素。

(3)使用memset函数初始化数组(或结构体)
头文件 string.h
memset(arr, 0, sizeof(arr));
该函数 按照字节赋值sizeof(类型) * 元素个数 ,注意sizeof(指针)可能会造成错误(指针和数组名不完全相同!)。一般只用 memset函数整型赋值0或-1,按字节就是全0或全f,赋其它值( 如:1 )未必得到预期结果。

给结构体初始化:
memset( & 结构体类型变量名, 0, sizeof(struct 结构体名));

(4)用数组变量给数组变量赋值(数组赋给数组),只能用for遍历数组元素进行赋值。
int a[] = {1, 2, 3};
int b[length];
for (i = 0; i < length; i ++) {
b[i] = a[i];
}

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

1.3. 计算数组长度
sizeof会给出所占据的字节数
数组单元个数 = sizeof(arr)/sizeof(arr[0])

使用场景:数组作为函数参数时,往往必须用另一个参数来传入数组大小,因为不能在a[]中给出数组大小而无法在函数内再利用sizeof来计算元素个数

2. 字符数组、字符串、字符串数组(关于指针可以先看C语言学习笔记10指针)

2.1. 概念

字符数组:形如:char word[] = {‘H’, ‘e’, ‘l’, ‘l’,‘o’,‘!’}; //6B大小
字符数组不是C语言的字符串,不能用字符串的方式做计算。(C语言没有string类型这样显式的字符串)
C语言的字符串可以用字符数组实现 —— char word[] = {‘H’, ‘e’, ‘l’, ‘l’,‘o’,‘!’,‘\0’}; //在字符数组元素最后加上’\0’或0,7B大小(数组长度),但计算字符串长度时0不会被记录,0只表示字符串的结束。【int的0是0x30,即ascii码的48。】

2.2. 字符串定义与使用

字符串变量定义:char * str = “Hello”; //使用双引号 编译器自动添0,占1B
通常指针形式定义的字符串位于低地址区域,不作为本地变量,只可读不可写(良好的操作系统的保护机制有关),因此我们用数组形式定义字符串:
       char word[] = “Hello”; // char line[10] = “Hello!”;

字符串以数组形式存在,通常用数组形式定义字符串而以指针/数组形式访问遍历。(C因为字符串是数组,所以不能用运算符对字符串运算,而java和python中可以对字符串简单计算)
头文件string.h中有许多处理字符串的函数。

C语言学习笔记04中printf输出字符串,这里就说明了两个相邻字符串会被自动连接成一个大字符串
在这里插入图片描述

它也能改写成如下,注意下行要顶格,否则会带空格

在这里插入图片描述

补充说明:下面s和s2指向的地址是一样的且不是本地变量,如果要对 s[0] = ‘X’; 是不可行的;原因是char类型指针指向字符串常量时本质是一个const char * 的常量,编译器因历史原因接收不加const,但试图改变该常量不行:

在这里插入图片描述
下面这样写,s和s2地址不一样,s数组是本地变量,s[0]可以被修改:
在这里插入图片描述

2.3. 字符的输入输出

单字符输入输出函数——int putchar(int)、int getchar(void)【int型的单个字符,出错返回EOF(-1)】
Shell服务程序:行编辑- ctrl+Z / ctrl+D (ctrl+C结束程序)】
在这里插入图片描述
getchar一个个读入。
scanf按格式符读入,使用%7s提高了安全性:
在这里插入图片描述

char * gets(char * ) 和 int puts(const char * ):
gets可以避免在输入缓冲区buffer中遗留\n,但是输入可以超过定义长度,不安全(而原数组截断到定义的长度)。【s初始化10个null】
puts成功返回非负值,失败返回EOF。
在这里插入图片描述

字符串常见错误:
使用指针定义 char * s; 字符串时,没有指向一个有效地址(需要有效的初始化)就直接使用,此时易导致程序出错。

2.4. 字符串数组

main函数参数:
在这里插入图片描述

字符串数组形式介绍(最后的 * s[] 应是存放指针的数组,内部数据不可修改):
在这里插入图片描述

月份英文单词输出练习 ——

const char * monthWords (int *p) {
	const char *s[] = {
		"Wrong Number",
		"January",
		"February",
		"March",
		"April",
		"May",
		"June",
		"July",
		"August",
		"September",
		"October",
		"November",
		"December"
	};
	if ( *p >= 1 && *p <= 12 ) {
		printf("%d月的英文单词:", *p);
	}
	else {
		*p = 0;
	}
	
	return s[*p];
}

int main() {
	printf("请输入月份:");
	int month;
	scanf("%d", &month);
	
	printf("%s\n", monthWords(&month));
//	switch (month) {
//		case 1: printf("January\n"); break;
//		case 2: printf("February\n"); break;
//		case 3: printf("March\n"); break;
//		case 4: printf("April\n"); break;
//		case 5: printf("May\n"); break;
//		case 6: printf("June\n"); break;
//		case 7: printf("July\n"); break;
//		case 8: printf("August\n"); break;
//		case 9: printf("September\n"); break;
//		case 10: printf("October\n"); break;
//		case 11: printf("November\n"); break;
//		case 12: printf("December\n"); break;
//	}
	
	return 0;
}

在这里插入图片描述
在这里插入图片描述

2.5. string.h中提供的字符串函数

各C发行版一般已经在string.h中定义好了如下常用函数:

strlen:返回字符串长度,不包含\0的结尾

strcmp:比较字符串大小,相等返回0,不相等返回首个不相等处字符的地址之差大于0返回1小于0返回-1)【数组的比较是地址的比较,所以永远是false】
int strncmp:可以只判断前几个字符是否相等 —— (s1,s2,n)

strcpy:复制字符串,strcpy(dst, src),dst和src是不可重叠的( * restrict C99),将src拷贝到dst,返回dst。【复制 char * dst = (char * )malloc(strlen(src) + 1); strcpy(dst, src); 】
strcat:strcat(dst_s1, src_s2),把s2拷贝到s1后面,形成一个长字符串,返回s1
建议使用安全版本:strncpy strncat —— (dst, src,n),拷贝字符个数
在这里插入图片描述
在这里插入图片描述

自己实现以上函数功能:

int mystrlen(const char *s) { //strlen
	int idx = 0;
	while ( s[idx] != '\0' ) {
		idx ++;
	}
	return idx;
}
int mystrcmp(const char *s1, const char *s2) { //strcmp
    //数组方式实现 定义一个下标变量
	//int idx = 0;
	//while ( s1[idx] == s2[idx] && s1[idx] != '\0' ) {
//		if ( s1[idx] != s2[idx] ) {
//			break;
//		}
//		else if ( s1[idx] == '\0') {
//			break;
//		}
		//idx ++;
	//}
	//return s1[idx]-s2[idx];
	
	//指针方式实现
	while ( *s1 == *s2 && *s1 != '\0' ) {
		s1 ++;
		s2 ++;
	}
	return *s1 - *s2;
}
char * mystrcpy(const char *src, char *dst) { //strcpy(dst, src) 源宿相反
	//数组方式
//	int idx = 0;
//	while ( src[idx] ) {
//		dst[idx] = src[idx]; //destination source
//		idx ++;
//	}
//	dst[idx] = '\0';
//	return dst;
    
	//指针方式
    char *ret = dst;
    while ( *src ) { // *src != '\0'
    	*dst++ = *src++;
    }
    *dst = '\0';
    return ret; //返回结果可以继续参与运算
}
char * mystrcat(char *dst, const char *src) { //strcat
    char *ret = dst;
    while ( *dst ) {
    	*dst ++;
    }
    while (*dst++ = *src++) 
		;
	return ret;
}

char * strchr(str, int c):在字符串中从左向右(strrchr 从右开始)找字符,返回(NULL) 没找到,只找第一个满足的【要继续找后面的:char * p = strchr(str, ‘a’); p = strchr(p+1, ‘a’);】
strstr:在字符串中找一个字符串,strcasestr:寻找过程忽略大小写

在这里插入图片描述
在这里插入图片描述

关于strchr函数的小套路(如何找第二个相同的字符,保留相同字符后的字符串输出):
在这里插入图片描述
在这里插入图片描述

保留相同字符之前的字符串输出:

char s[] = "hello";
char *p = strchr(s, 'l');
char c = *p;
*p = '\0';
char *t = (char *)malloc(strlen(s) + 1);
strcpy(t, s);
*p = c; //恢复s
printf("%s %s\n", t, s);
free(t);

在这里插入图片描述

3. 二维数组

通常可以将二维数组理解为矩阵——int a [3][5] 是一个整型的3行5列(根据其在内存中的排列考量)的矩阵,初始化二维数组时必须给出列数,如:
int a[][5] = {{0,1,2,3}, {2,3,4,5,6,}, }; (行数会由编译器来数,此处是2行;未明确初始化赋值的单元补0;C99也可以用定位[i][j]=n)。
如果每行不加{} (int a[][5] = {0,1,2,3,4,2,3}; ),就表示从左上角到右下角逐行(自左向右)填充值。

二维数组的遍历借助两层for循环,a[i][j]表示第 i 行第 j 列上的单元(同样从0开始数) 。
a[i,j]是会先计算表达式 i,j 得到 j ,即a[j] ,它不能正确表达一个二维数组的单元,是错误的。

游戏1:井字棋 tic-tac-toe 展示部分代码

判断输赢(4条线):


	const int size = 3; // #define SIZE 3
	int board[size][size]; //少用magic魔法数/飞来数,尽量用可以标识的变量
	int i, j;
	int numOfX, numOfO; // 1-X    0-O
	int result = -1; // 1 X win    0 O win    -1 no win


    //check row
	for ( i = 0; i < size && result == -1; i ++ ) {
		numOfO = numOfX = 0;
		for ( j = 0; j < size; j ++ ) {
			if ( board[i][j] == 1 ) {
				numOfX ++;
			}
			else {
				numOfO ++;
			}
		}
		if ( numOfO == size ) {
			result = 0;
		}
		else if ( numOfX == size ) {
			result = 1;
		}
	}
	//check column
	if ( result == -1 ) {
		for ( j = 0; j < size && result == -1; j ++ ) {
			numOfO = numOfX = 0;
			for ( i = 0; i < size; i ++ ) {
				if ( board[i][j] == 1 ) {
					numOfX ++;
				}
				else {
					numOfO ++;
				}
			}
			if ( numOfO == size ) {
				result = 0;
			}
			else if ( numOfX == size ) {
				result = 1;
			}
		}
	}
	//check diagonal line +-
	numOfO = numOfX = 0;
	for ( i = 0; i < size; i ++ ) {
		if ( board[i][i] == 1 ) {
			numOfX ++;
		}
		else {
			numOfO ++;
		}
	}
	if ( numOfO == size ) {
		result = 0;
	}
	else if ( numOfX == size ) {
		result = 1;
	}
	
	numOfO = numOfX = 0;
	for ( i = 0; i < size; i ++ ) {
		if ( board[i][size-i-1] == 1 ) {
			numOfX ++;
		}
		else {
			numOfO ++;
		}
	}
	if ( numOfO == size ) {
		result = 0;
	}
	else if ( numOfX == size ) {
		result = 1;
	}
游戏2:控制台打飞机 展示部分代码
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <windows.h>

/*
***打飞机游戏设计***
*/

//宏定义界面尺寸
#define Width 36 //宽36列
#define Height 24 //高24行

//宏定义敌人数量
#define EnemyNum 5

//全局变量声明与定义
int canvas[Height][Width] = { 0 };//用二维数组记录界面中对应的元素,初始化为0。不需要具体定义攻击波位置了,也不用考虑未发射攻击波情况-1。
//二维数组内的数据含义:0空格,1角色*,2攻击波|(二维数组实现了连续输出攻击波),3敌人&
int pos_x, pos_y;//角色位置坐标
int enemy_x[EnemyNum], enemy_y[EnemyNum];//敌人位置坐标
int EXP;//角色获得的经验值,注:exp报错built-in function可能是因为exp是标准库函数名
int EnemyNSpeed;//敌人新移速值
int attk_big;//大攻击波(宽度变宽)

//特殊库函数声明
//<windows.h>中的函数gotoxy:作用是将光标移至(x,y)处,可以代替system("cls");清屏
void gotoxy(int x, int y);
//<windows.h>中的函数HideCursor:作用是隐藏光标
void HideCursor();

//自定义函数声明
void Init();//界面数据初始化
void Show();//界面展示
void UpdateWithoutInput();//无输入更新
void UpdateWithInput();//有输入更新

//***入口函数***
int main()
{
	system("color FA");//背景-亮白色F,前景-淡绿色A
	Init();//首先初始化数据
	//不断循环操作执行过程
	while (1) {
		Show();//界面展示
		UpdateWithoutInput();//先更新与用户输入无关的部分
		UpdateWithInput();//再更新与用户输入有关的部分
	}
	return 0;
}

//有输入更新函数定义
void UpdateWithInput() {
	char input;//定义字符变量input存放输入的字符

	//当按键时执行,vs里必须加_
	if (_kbhit()) {
		//无回车瞬时读取输入的字符,vs里必须加_
		input = _getch();

		//上w、下s、左a、右d方向移动
		if (input == 'w') {
			canvas[pos_y][pos_x] = 0;//原位置先归零
			pos_y--;//角色运动
			//不能出上边界
			if (pos_y < 0) {
				pos_y = 0;
			}
			canvas[pos_y][pos_x] = 1;//新位置显示
		}
		if (input == 's') {
			canvas[pos_y][pos_x] = 0;
			pos_y++;
			//不能出下边界
			if (pos_y > Height - 1) {
				pos_y = Height - 1;
			}
			canvas[pos_y][pos_x] = 1;
		}
		if (input == 'a') {
			canvas[pos_y][pos_x] = 0;
			pos_x--;
			//不能出左边界
			if (pos_x < 0) {
				pos_x = 0;
			}
			canvas[pos_y][pos_x] = 1;
		}
		if (input == 'd') {
			canvas[pos_y][pos_x] = 0;
			pos_x++;
			//不能出右边界
			if (pos_x > Width - 1) {
				pos_x = Width - 1;
			}
			canvas[pos_y][pos_x] = 1;
		}
		if (input == ' ') {
			//攻击状态
			int left, right;//定义左右攻击扩展半径
			left = pos_x - attk_big;
			if (left < 0) {//防止攻击波范围越界后在屏上循环显示
				left = 0;
			}
			right = pos_x + attk_big;
			if (right > Width - 1) {
				right = Width - 1;
			}
			int i;//记录攻击波发射列
			for (i = left; i <= right; i++) {
				canvas[pos_y - 1][i] = 2;//攻击波发射于角色的上一行,且与发射位置同列
			}
		}
		//按esc键暂停,按任意键恢复。esc键的ascaii码值为27
		if (input == 0x1b) {
			system("pause");//强制等待命令暂停程序
		}
	}
}

在这里插入图片描述

更高维的,如三维、四维数组和二维数组类似考虑和想象。

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个将用户输入的字符串转换成二维数组C语言代码,使用了子函数的形式实现。代码中包含了详细的注释,以帮助理解代码实现过程。 ```c #include <stdio.h> #include <string.h> #define MAX_SIZE 100 // 函数声明 void stringTo2DArray(char *input, char array[][MAX_SIZE], int *rows); int main() { char input[MAX_SIZE]; char array[MAX_SIZE][MAX_SIZE]; int rows = 0; printf("Enter a string: "); gets(input); // 获取用户输入的字符串 stringTo2DArray(input, array, &rows); // 调用子函数将字符串转为二维数组 // 打印二维数组 for(int i = 0; i < rows; i++) { printf("%s\n", array[i]); } return 0; } // 将输入的字符串转为二维数组的子函数 void stringTo2DArray(char *input, char array[][MAX_SIZE], int *rows) { char *token; token = strtok(input, " "); // 将字符串按照空格分割成独的字符 while(token != NULL) { strcpy(array[*rows], token); // 将分割后的字符存储到二维数组中 (*rows)++; // 行数加1 token = strtok(NULL, " "); // 继续分割下一个字符 } } ``` 在主函数中,我们首先声明了一个字符数组`input`、一个二维字符数组`array`和一个整变量`rows`,分别用于存储用户输入的字符串、存储二维数组的各个元素和记录二维数组的行数。 接着,我们使用`gets()`函数获取用户输入的字符串,并将其作为参数传递给`stringTo2DArray()`函数,该函数用于将输入的字符串转换成二维数组。 在`stringTo2DArray()`函数中,我们首先定义了一个指针变量`token`,用于存储按照空格分割后的字符。 然后,我们使用`strtok()`函数将输入的字符串按照空格分割成独的字符,并将分割后的字符存储到二维数组中。每次存储完一个字符后,将行数`rows`加1,继续分割下一个字符,直到字符串中的所有字符都分割完毕。 最后,在主函数中,我们通过一个循环遍历二维数组中的各个元素,并使用`printf()`函数按照指定格式打印出来。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值