C语言初阶练习题

练习1:写代码将三个数从大到小输出

#define _CRT_SECURE_NO_WARNINGS 1
#include 
//写代码将三个数从大到小输出
void exchange(int* x, int* y)
{
	int tmp = 0;
	 tmp = *x;
	*x = *y;
	*y = tmp;
}
int main() {
	int a = 0;
	int b = 0;
	int c = 0;
	scanf("%d %d %d", &a, &b, &c);
	if (a < b)
	{
		exchange(&a, &b);	
	}
	if (a < c)
	{
		exchange(&a, &c);
	}
	if (b < c)
	{
		exchange(&b, &c);
	}
	printf("%d %d %d", a, b, c);
}

总结:常用交换两个变量的值的方法

引入第三个变量作为中间变量

int tmp = 0;
     tmp = x;
    x = y;
    y = tmp;

练习2:求10个整数中的最大值

#define _CRT_SECURE_NO_WARNINGS 1
#include 
//求10个整数中的最大值
int main() 
{
	//获取10个数
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	int i = 0;
	for (i = 0; i <= 9; i++)
	{
		scanf("%d", &arr[i]);
	}
	//两两进行比较,较大的赋值给max
	int j = 0;
	int max = arr[0];
	while (j < 10)
	{
		if (max < arr[j])
		{
			max = arr[j];
		}
		j++;
	}
	printf("%d ", max);
}

总结:求多个数最大值的方法

常见的求多个数里的最大值,创建max变量(擂台),两两进行比较,较大的赋值给max(打赢的继续站在擂台上),最后输出max即可,俗称打擂台

练习:求2个数的最小公倍数和最大公约数

#define _CRT_SECURE_NO_WARNINGS 1
#include 
int main()
{
    long long n = 0;
    long long m = 0;
    while (scanf("%lld %lld", &n, &m) == 2)
    {
        //方法1:
        求最小公倍数
        //long long lcd = n;
        //int i = 1;
        //while (lcd % m)
        //{
        //    lcd = n * i;
        //    i++;
        //}
        有了最小公倍数,利用数学公式gcd=n*m/lcd
        //long long gcd = n * m / lcd;
        //方法2:辗转相除法求最大公约数,效率比第一种方法高
        long long i = n;
        long long j = m;
        long long r = 0;
        while (r=i % j)//当余数为0时,j就是最大公约数
        {
            i = j;
            j = r;
        }
        long long gcd = j;
        long long lcd = n * m / gcd;//利用最大公约数和最小公倍数的数学关系公式
        printf("%lld %lld", lcd , gcd);//%lld表示以long long类型格式打印
    }
    return 0;
}

总结:求最小公倍数的方法 

方法1:多个数中任意一个数不停*i++(i从1开始)的结果ret对其他数进行取余,直至余数==0,ret就是最小公倍数

方法2:先用辗转相除法求最大公约数,再根据公式求最小公倍数

练习3:打印出乘法口诀表

#define _CRT_SECURE_NO_WARNINGS 1
//打印乘法口诀表
//1*1=1
//2*1=2 2*2=4
//3*1=3 3*2=6 3*3=9
//......
//9*1=9        .......       9*9=81
#include 
int main()
{
	int i = 0;
	for (i = 1; i <= 9; i++)
	{
		int j = 0;
		for (j = 1; j <= i; j++)
		{
			printf("%d*%d=%2d ", i, j, i * j);
		}
		printf("\n");
	}
}

总结:int i = 0;
    for (i = 1; i <= 9; i++)
    {
        int j = 0;
        for (j = 1; j <= i; j++)  
        {
            printf("%d*%d=%2d ", i, j, i * j);
        }

总结

标黄处j<=i中的i非常关键,因为i是外层循环的调整变量,才能得到正方形的一半,乘法口诀表形状就是正方形的一半,如果写成j<=9,只能得到一个完整的正方形

另外%2d用于2位右对齐,%-2d用于2位左对齐

练习4:求n的阶乘及求1!+2!+3!+......+n!

方法1:循环

#define _CRT_SECURE_NO_WARNINGS 1
#include 
//练习:求n的阶乘
//方法1:循环
int main(){
	int n = 0;
	int i = 1;
	int ret = 1;
	int sum = 0;
	scanf("%d", &n);
	for (i = 1; i <= n; i++) {
		ret = ret * i;
 //练习:求1!+2!+3!+......+n!
		sum += ret;
	}
	printf("%d\n", ret);
	printf("%d", sum);

return 0;
}

 求n的阶乘方法2:函数递归

#define _CRT_SECURE_NO_WARNINGS 1
//用函数的递归求n的阶乘
//					1,			n=1
 //数学公式:f(n)={
//					n*f(n-1),	n>1
int f(int n)
{
	if (n == 1)
	{
		return 1;
	}
	else
	{
		return n * f(n - 1);
	}
}
#include 
int main()
{
	int n = 1;
	while (n >= 1)
	{
		scanf("%d", &n);
		printf("%d\n", f(n));
	}
	return 0;
}

总结:

写函数递归,基本就是按照数学公式在写, 比如f(n)和f(n-i)有某种数学关系,根据公式写就行

练习5:逆序排列字符串,非单纯逆序打印,是要改变字符串的内容

要求:多组输入(即循环输入),输入字符串长度不超过10000

方法1:循环

#define _CRT_SECURE_NO_WARNINGS 1
//逆序排列字符串,非单纯逆序打印,是要改变字符串的内容
#include 
#include 
void reverse(char arr[])
{
	int left = 0;
	int right = strlen(arr) - 1;
	while (left < right)
	{
		//交换首尾元素
		int tmp = arr[left];
		arr[left] = arr[right];
		arr[right] = tmp;
		left++;
		right--;
	}
}
//方法1:循环
int main()
{
	char arr[10000] ={0};
    //多组输入
    while( gets(arr) != NULL )//防止输入语句死循环
    {
	    reverse(arr);
	    printf("%s\n", arr);
    }
    return 0;
}

 方法2:函数递归

//方法2:函数递归
void Reverse(char arr[],int left,int right)
{														
	//拆成首尾交换和Reverse(char arr[],left+1,right-1)
if (left < right)
	{
	    char tmp = arr[left];
	    arr[left] = arr[right];
	    arr[right] = tmp;
	
		Reverse(arr, left + 1, right - 1);//注意不是left++;right--
	}
}
int main()
{
	char arr[10000] = {0};
    while(gets(arr) !=NULL)
    {
	    int left = 0;
	    int right = strlen(arr) - 1;
	    Reverse(arr, left, right);
	    printf("%s\n", arr);
    }
    return 0;
}

总结:

        思考函数递归时不要局限于只设置1个参数,设置多参数时,可能思路会更清晰,递归就是每递进一次,参数都在发生改变,所以考虑把递进里改变的变量设置成参数

        字符串属于数组里非常特殊的存在,操作字符串时最好使用字符串特有的库函数,比如操作字符串时,gets(arr)函数就比scanf("%s",arr)要好,strlen(arr)就比sizeof(arr)/sizeof(arr[0])要好,

因为scanf遇到空格就停止,所以不能输入有空格的字符串内容,strlen不会把字符串结束标志'\0'

算入长度,而sizeof(arr)/sizeof(arr[0])会,且在此题中arr[10000]默认初始化10000个'\0',会导致right错误,因为我们只逆序我们输入的一堆’\0'前面的n个字符,而不是逆序10000个字符

练习:将一句话的单词进行倒置,标点不倒置。

比如 I like beijing. 经过函数后变为:beijing. like I

思路:先全部字符逆序,再一个一个单词内部逆序。例如:

I like beijing.
.gnijieb ekil i
beijing. like i

#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
//将一句话的单词进行倒置,标点不倒置。比如 I like beijing. 经过函数后变为:beijing. like I
//思路:先全部字符逆序,再一个一个单词内部逆序
void reverse(char* left , char* right)
{
	while (left < right)
	{
		char tmp = *left;
		*left = *right;
		*right = tmp;
		left++;
		right--;
	}
}
int main()
{
	char arr[100] = { 0 };
	gets(arr);
	//先全部字符逆序
	reverse(arr,arr+strlen(arr)-1);
	//printf("%s\n", arr);

	//单词内部逆序
	char* left = arr;
	char* right = left;
	while (*left!='\0')
	{
		while (*right != ' '&&*right!='\0')
		{
			right++;
		}
		reverse(left, right - 1);
		left = right + 1;
		right = left;
	}
	printf("%s", arr);
	return 0;
}

总结:

在单个数组内部重复进行某些修改,起修改作用的自定义函数参数最好使用双指针(首尾端指针),这样整个数组就可以重复使用,不用创建很多数组,参数设置为指针比设置为下标更精简

练习6:写一个递归函数digit_sum(n),输入一个非负整数,返回组成他的数字之和

#define _CRT_SECURE_NO_WARNINGS 1
//写一个递归函数digit_sum(n),输入一个非负整数,返回组成他的数字之和
//思考怎样得到比如1729的每一位,并求和
//个位是最好获取的,所以拆成个位:1729%10和digit_sum(172),172就是1729/10的结果
#include 
int digit_sum(unsigned int a)
{ 
	if (a > 9) {
		int sum = a % 10 + digit_sum(a / 10);
		return sum;
	}
	return a;
}
int main()
{
	unsigned int a = 0;
	scanf("%u", &a);
	int ret=digit_sum(a);
	printf("%d\n", ret);
}

总结:

个位是最好获取的,所以拆成个位:1729%10和digit_sum(172)的和,172就是1729/10的结果 

unsigned int是防止你输入负数,%u是unsigned int的输入和打印形式

练习7:写一个函数实现n的k次方,用函数递归实现

数学公式

C语言初阶练习题_第1张图片

#define _CRT_SECURE_NO_WARNINGS 1
//写一个函数实现n的k次方,用函数递归实现
#include 
double Pow(int n, int k)
{
	if (k == 0)
	{
		return 1.0;
	}
	else if (k > 0)
	{
		return n* Pow(n, k - 1);
	}
	else
	{
		return 1.0 / Pow(n, -k);
	}
}
int main()
{
	int n = 0;
	int k = 0;
	scanf("%d %d", &n, &k);
	printf("%lf\n",Pow(n, k));
	return 0;
}

练习:小乐乐走台阶 递归实现

小乐乐上课需要走n阶台阶,因为他腿比较长,所以每次可以选择走一阶或者走两阶,那么他一共有多少种走法?

如果只有1个台阶,就只有1种走法,如果有2个台阶,有2种走法,当有n个台阶,

若第一步走1阶,那还剩n-1个台阶,有f(n-1)种走法

若第一步走2阶,还剩n-2个台阶,有f(n-2)种走法

C语言初阶练习题_第2张图片

#define _CRT_SECURE_NO_WARNINGS 1
#include 
int f(int n)
{
	if (n <= 2)
	{
		return n;
	}
	else
	{
		return f(n - 1) + f(n - 2);
	}
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	printf("%d\n", f(n));
}

练习:判断一个数是否是素数(除了本身和1外,没有其他数能整除)

#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
//练习1:判断一个数是否是素数(除了本身和1外,没有其他数能整除)
//写一个函数
int is_prime(int n)
{
	int j = 0;
	for (j = 2; j <= sqrt(n); j++)
	{
		if (n % j == 0)
		{
			return 0;
		}
	}
	return 1;
}
int main()
{
	int i = 0;
	scanf("%d", &i);
	if (is_prime(i))
	{
		printf("这个数是素数");
	}
	else
	{
		printf("这个数不是素数");
	}
	return 0;
}

 练习:判断输入的字符是不是字母。

#define _CRT_SECURE_NO_WARNINGS 1
//
//输入描述:
//多组输入,每一行输入一个字符。
//输出描述:
//针对每组输入,输出单独占一行,判断输入字符是否为字母,输出内容详见输出样例。
#include 
int main()
{
	char ch = 0;
	// %c前面加个空格表示跳过下一个字符之前的所有字符,不用getchar()清理缓冲区
	while (scanf(" %c", &ch) == 1)//防止死循环
	{
		if (ch >= 'A' && ch <= 'z')
		{
			printf("%c is an alphabet.\n", ch);
		}
		else
		{
			printf("%c is not an alphabet.\n", ch);
		}
		//getchar();//清理缓冲区,防止换行\n被scanf读取
	}
	return 0;
}

总结:

防止换行 '\n' 被scanf读取的方法有

        1.清理缓冲区(常用 getchar() 来清理)

        2.  %c前面加个空格scanf(" %c", &ch)表示读取时跳过下一个字符之前的所有空白字符

 多组输入时防止键盘输入死循环常用方法:

        多组输入的意思就是循环输入

        通常在判断条件加一句scanf("%c",&ch) !=EOF或scanf("%c",&ch)==1,1表示scanf接收的元素个数,如果输入的是2个数则是2
        或getchar() != EOF

        例如:while (scanf(" %c", &ch) != EOF)//防止死循环或while(scanf("%c %c" ,&ch1,&ch2)==2)

练习:打印1000-2000年的闰年

#define _CRT_SECURE_NO_WARNINGS 1
#include 
int  is_leap_year(int n) {
	
		if ((n % 4 == 0 && n % 100 != 0) || (n % 400 == 0))
		{
			return 1;
		}
		return 0 ;
}
int main() {
	//练习2:打印1000-2000年的闰年
	// 闰年的判断规则:可以被4整除,但不能被100整除;或可以被400整除
	//首先获取1000-2000的数
	int i = 0;
	for (i = 1000; i <= 2000; i++)
	{
		if(is_leap_year(i))
		printf("%d ",i);
	}
	return 0;
}

练习:实现 编写代码,演示多个字符从两端移动,向中间汇聚

#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include 
#include 
#include 
int main() 
{
//创建2个数组
	char arr1[] = "welcome to bit!";
	char arr2[] = "###############";
	//获取下标
	int left = 0;
	int right = strlen(arr1) - 1;
	while (left <= right) 
	{
		arr2[left] = arr1[left];
		arr2[right] = arr1[right];
		遍历arr2
		//int i = 0;
		//for (i = 0; i < strlen(arr2); i++) 
		// {
		//	printf("%c", arr2[i]);
		//}
		//printf("\n");
		//其实不需要遍历,因为字符串可以直接打印
		printf("%s\n", arr2);
			left++;
			right--;
			Sleep(1000);//库函数休眠,单位是毫秒ms
			system("cls");//system执行系统命令行库函数,cls清屏命令行
	}
	printf("%s", arr2);
return 0;
}

练习:编写代码实现,模拟用户登录情景,并且只能登录三次。

只允许输入三次密码,如果密码正确则提示登录成,如果三次均输入错误,则退出程序。

int main() 
{
	char password[10] = { 0 };
	//输入密码
	int i = 0;
	for (i = 1; i <= 3; i++)
	{
		scanf("%s", password);
		//比较密码
		if (strcmp(password, "123456") == 0)
		{
			printf("密码正确");
			break;
		}
		else
		{
			printf("密码错误,请重新输入\n");
		}
	}
	if (i > 4)
	{
		printf("输入错误密码超3次,已强制退出");
	}
 return 0;
}

练习:猜数字游戏实现

#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include 
#include 
#include 

void menu() 
{
	printf("**********************************\n");
	printf("***********  1.play     **********\n");
	printf("***********  0.exit     **********\n");
	printf("**********************************\n");
}

void game()
{
	//电脑产生一个0-100的随机值
	srand((unsigned) time(NULL));
	int num = rand() % 100 + 1;
	//猜一个数字0-100
	int guess = 0;
	while (1)
	{
		scanf("%d", &guess);
		if (num > guess)
		{
			printf("猜小了\n");
		}
		else if (num < guess)
		{
			printf("猜大了\n");
		}
		else
		{
			printf("恭喜你,猜对了\n");
			break;
		}
	}
}
int main() 
{
int input = 0;
	do 
	{
		menu();
		//选择是否玩游戏,请输入1或者0
		scanf("%d", &input);
		switch (input) 
		{
		case 1:
			game();
			break;
		case 0:
				break;
		default:
			printf("输入有误,请重新输入\n");
			break;
		}

	} while (input);
		return 0;
}

总结:为了实现一个变量a的值总是比某个值n小 ,通常用a%n,得到的值就是0至n-1

练习:冒泡排序把键盘输入的10个数逆序排序

C语言初阶练习题_第3张图片

#define _CRT_SECURE_NO_WARNINGS 1
//用冒泡排序法把键盘输入的10个数逆序排序
#include 
//创建冒泡排序函数
void bubble_sort(int arr[],int sz)
{
	//算需要几趟冒泡排序
	//int sz = sizeof(arr) / sizeof(arr[0]);数组传参,函数内部计算sizeof(arr)其实是算的指针大小
	//所以不能这样写,考虑写在函数外部且函数内部可用,所以用参数的方式最合适
    int flag=1;//用于判断输入的数组是否本来就是逆序排序的,如果是,就不需要进行第二趟冒泡排序了
	int i = 0;
	for (i = 0; i < sz-1; i++)
	{
		//算一趟冒泡排序有几对比较的数
		int j = 0;
		for (j = 0; j 

总结:

从左至右两两成对进行比较,不符合想要的顺序则交换2个数字,n个数字需要n-1趟冒泡排序(行i),第1趟冒泡排序需要n-1对比较的数 ,第2趟需要n-2对比较的数.......第n-1趟需要1对比较的数(列j)。列号是在随着行号增加在减小1,所以内层循环发判断语句是j

练习:输入两个升序排列的序列,将两个序列合并为一个有序序列并输出。

方法1:先合并再排序

int main()
{
	int arr1[2000] = { 0 };
	int arr2[1000] = { 0 };
	int n = 0;
	int m = 0;
	scanf("%d %d", &n, &m);
	int i = 0;
	for (i = 0; i < n; i++)
	{
		scanf("%d", &arr1[i]);
	}
	for (i = 0; i < m; i++)
	{
		scanf("%d", &arr2[i]);
	}
    //合并到arr1
	for (i = n; i < n + m; i++)
	{
		arr1[i] = arr2[i - n];
	}
	//冒泡排序
	bubble_sort(arr1, n + m - 1);
    for (i = 0; i < n + m; i++)
	{
		printf("%d ", arr1[i]);
	}
	return 0;
}

方法2:边排序边合并

int main()
{
	int arr1[2000] = { 0 };
	int arr2[1000] = { 0 };
	int n = 0;
	int m = 0;
	scanf("%d %d", &n, &m);
	int i = 0;
	for (i = 0; i < n; i++)
	{
		scanf("%d", &arr1[i]);
	}
	for (i = 0; i < m; i++)
	{
		scanf("%d", &arr2[i]);
	}
//方法2:边排序边合并
	int arr[3000] = { 0 };
	int j = 0;
	int k = 0;
	for (i = 0,j = 0; i < n && j < m; )
	{
		if (arr1[i] < arr2[j])
		{
			arr[k++] = arr1[i++];
		}
		else 
		{
			arr[k++] = arr2[j++];
		}
	}
	if (i < n)//说明arr1内部还有剩余元素没比较
	{
		for (; i < n; i++)//遍历剩余元素
		{
			arr[k++] = arr1[i];
		}
	}
	if (j < m)//说明arr2内部还有剩余元素没比较
	{
		for (; j < m; j++)//遍历剩余元素
		{
			arr[k++] = arr2[j];
		}
	}
	for (i = 0; i < n + m; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

 总结:方法2仅适用于输入的本来就是有顺序的数组,因为存在剩余元素未比较,即未排序。如果输入的是乱序数组,只能用方法1

练习:写一个函数可以左旋字符串中的k个内容

方法1:

C语言初阶练习题_第4张图片

#include 
#include 
void left_rotate(char arr[], int k)
{
    k %= strlen(arr);//为了保证k小于字符串长度
	//第一步:将a取出来放到临时空间
	int i = 0;
	for (i=0; i < k; i++)
	{
		char tmp = arr[0];
		//第二步:将a后面所有的字符往前挪一步
		int j = 0;
		for (j = 0; j < strlen(arr)-1; j++)
		{
			arr[j] = arr[j + 1];
		}
		//第三步:将tmp放到最后
		arr[j] = tmp;
	}
}
int main()
{
	char arr[6] = "abcde";
	int k = 0;
	scanf("%d", &k);
	left_rotate(arr, k);
	printf("%s\n", arr);
	return 0;
}

方法2: 

C语言初阶练习题_第5张图片

#include 
#include 
#include 
void reverse(char* left, char* right)
{
	assert(left && right);
	while (left < right)
	{
		char tmp = *left;
		*left = *right;
		*right = tmp;
		left++;
		right--;
	}
}
void left_rotate(char arr[], int k)
{
	k %= strlen(arr);//为了保证k小于字符串长度
	reverse(arr, arr + k - 1);//第一步:逆序k个字符
	reverse(arr + k, arr + strlen(arr) - 1);//第二步:逆序剩余部分的字符
	reverse(arr, arr + strlen(arr) - 1);//第三步:逆序整个字符串
}
int main()
{
	char arr[6] = "abcde";
	int k = 0;
	scanf("%d", &k);
	left_rotate(arr, k);
	printf("%s\n", arr);
	return 0;
}

练习:写一个函数判断一个字符串arr2能否由另一个字符arr1旋转而来 

分析:穷举arr1左转的可能性和arr2一一对比,左旋k个字符和右旋len-k个字符的结果一样,所以左旋的所有可能性就是旋转的所有可能性。

方法1:真穷举

#include 
#include 
int is_rotate(char arr1[], char arr2[])
{
	//穷举arr1旋转的所有可能性和arr2对比
	int len = strlen(arr1);
	int i = 0;
	for (i = 0; i < len - 1; i++)//arr1左旋len-1次
	{
		char tmp = arr1[0];
		int j = 0;
		for (j = 0; j < len - 1; j++)
		{
			arr1[j] = arr1[j + 1];
		}
		arr1[len - 1] = tmp;
		if (strcmp(arr1, arr2) == 0)//有一种旋转后结果和arr2相等则证明arr2可由arr1旋转而来
		{
			return 1;
		}
	}
	return 0;//穷举完没有一种旋转结果和arr2相等,则arr2不是由arr1旋转而来
}
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "defabc";
	int ret = is_rotate(arr1, arr2);
	if (ret == 1)
	{
		printf("yes\n");
	}
	else 
	{
		printf("no\n");
	}
	return 0;
}

方法2: 用字符串库函数变相穷举

arr1追加一个自己,结果就包含了arr1旋转的所有可能性

#include 
#include 
int is_rotate(char arr1[], char arr2[])
{
	int len1 = strlen(arr1);
	int len2 = strlen(arr2);
	if (len1 != len2)//如果arr1和arr2长度不同,那一定不可能是旋转得来的
		return 0;//return自带分支效果
	strncat(arr1, arr1, len1);//arr1追加一个自己,结果就包含了arr1旋转的所有可能性
	if (strstr(arr1, arr2) != NULL)//寻找子串
	{
		return 1;
	}
	else 
	{
		return 0;
	}
}
int main()
{
	char arr1[20] = "abcdef";
	char arr2[] = "defab";
	int ret = is_rotate(arr1, arr2);
	if (ret == 1)
	{
		printf("yes\n");
	}
	else
	{
		printf("no\n");
	}
	return 0;
}

练习.三子棋游戏

test.c文件

#define _CRT_SECURE_NO_WARNINGS 1
//写一个三子棋游戏
#include "game.h"
void game(char board[ROW][COL], int row, int col)
{
	char ret = 0;
	//写个初始化函数,调用初始化数组
	init_board(board, ROW, COL);
	//打印显示初始棋盘
	display_board(board, ROW, COL);
	while (1) {
		//玩家先出棋,函数
		player_move(board, ROW, COL);
		//判断输赢函数
		ret = is_win(board, ROW, COL);
		display_board(board, ROW, COL);
		if (ret != 'c')
		{
			break;
		}

		//电脑出棋,函数
		computer_move(board, ROW, COL);
		//判断输赢函数
		ret = is_win(board, ROW, COL);
		display_board(board, ROW, COL);
		if (ret != 'c')
		{
			break;
		}
	}
	if (ret == '*')
	{
		printf("玩家赢\n");
	}
	else if (ret == '#')
	{
		printf("电脑赢\n");
	}
	else
	{
		printf("平局\n");
	}
	display_board(board, ROW, COL);

}

int main()
{//游戏至少进行一次,所以用do while循环
	//三子棋看形状是个正方形,肯定要用到二维数组
	//创建二维数组
	char board[ROW][COL] = { 0 };
	//设置rand随机数起始点
	srand((unsigned int) time(NULL));
	int input = 0;
	do
	{
		//首先打印游戏菜单
		menu();
		//键盘输入选择
		printf("请输入1或0 :");
		scanf("%d", &input);
		//判断输入的数是否是1或者0,决定是否进入游戏
		if (input == 1)
		{
			game(board, ROW, COL);
			
		}
		else if (input == 0)
		{
			printf("退出游戏");
		}
		else
		{
			printf("输入错误,请输入1或0\n");
		}
	} while (input);
}

game.c文件

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
	printf("***********************\n");
	printf("*****1.play 0.exit*****\n");
	printf("***********************\n");
}

void init_board(char board[ROW][COL],int row,int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			board[i][j] = ' ';
		}
	}
}

void display_board(char board[ROW][COL], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		//这样就写的太死了,以后想改成5x5的棋盘或10x10的棋盘之类就很麻烦
		// //先打印数据
		//printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
		//if (i < row - 1) 
		//{
		//	//再打印分割线
		//	printf("---|---|---\n");
		//}
		//改进
		//先打印第一行数据
		for (j = 0; j < col; j++)
		{
			printf(" %c ", board[i][j]);
			if (j < col - 1)//最后一列不打印
			{
				printf("|");
			}
		}
		printf("\n");
		//再打印行分割线
		if (i < row-1)//最后一行不打印
		{
			for (j = 0; j < col; j++)
			{
				printf("---");
				if (j < col - 1)//最后一列不打印
				{
					printf("|");
				}
			}
			printf("\n");
		}
	}
	
}

void player_move(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	//键盘输入坐标
	while (1)
	{
		printf("玩家下棋,请输入您要下棋的坐标\n");
		scanf("%d,%d", &x, &y);
		//先判断输入的坐标是否超出棋盘范围
		if (x >= 1 && x <= col && y >= 1 && y <= row)
		{
			//再判断输入的坐标上有无棋子
			if (board[x-1][y-1] == ' ')
			{
				board[x-1][y-1] = '*';
				break;
			}
			else
			{
				printf("此坐标被占用,请另选坐标\n");
			}
		}
		else
		{
			printf("输入的坐标超出棋盘范围,请另选坐标\n");
		}
	}
}

void computer_move(char board[ROW][COL], int row, int col)
{
	printf("电脑下棋\n");
	while (1)
	{
		//电脑随机产生1,1到3,3的坐标
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		//再判断输入的坐标上有无棋子
		if (board[x - 1][y - 1] == ' ')
		{
			board[x - 1][y - 1] = '#';
			break;
		}
	}
}
//判断棋盘是否满了的函数,满了返回1,没满返回0
int is_full(char board[ROW][COL], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			if (board[i][j] == ' ')
			{
				return 0;
			}
		}
	}
	return 1;
}
char is_win(char board[ROW][COL], int row, int col)
{
	//三子棋输赢有4种结果,1玩家赢;2电脑赢;3棋盘下满了,未分胜负,平局;4棋盘还未下满,继续下
	// 规定如果玩家赢了则返回*,电脑赢则返回#,平局返回'Q',继续返回'c'
	//判断是否有一方赢了
	//判断是否有哪行是连续三颗*或#
	int i = 0;
	for (i = 0; i < row; i++)
	{
		if (board[i][0] == board[i][1] && board[i][1] == board[i][2]&& board[i][0] != ' ')
		{
			return board[i][0] ;
		}
	}
	//判断是否有哪列是连续三颗*或#
	int j = 0;
	for (j = 0; j < col; j++)
	{
		if (board[0][j] == board[1][j] && board[1][j] == board[2][j]&& board[0][j] != ' ')
		{
			return board[0][j] ;
		}
	}
	//判断是否哪条对角线是连续三颗*或#
	if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[0][0] != ' ')
	{
		return board[0][0];
	}
	if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2] != ' ')
	{
		return board[0][2];
	}
	//判断是否平局,需要遍历数组,没有一个元素是' '就行
	if (is_full(board, ROW, COL))
	{
		return 'Q';
	}
	return 'c';
}

game.h头文件

#pragma once
#include 
#include 
#include 

#define ROW 3
#define COL 3
void menu();
void init_board(char board[ROW][COL], int row, int col);
void display_board(char board[ROW][COL], int row, int col);
void player_move(char board[ROW][COL], int row, int col);
void computer_move(char board[ROW][COL], int row, int col);
char is_win(char board[ROW][COL], int row, int col);
void game(char board[ROW][COL], int row, int col);

练习:扫雷游戏

test.c文件

#define _CRT_SECURE_NO_WARNINGS 1
//写一个扫雷游戏
#include "game2.h"

void menu()
{
	printf("*************************\n");
	printf("*******	 1.play	 ********\n");
	printf("*******	 0.exit	 ********\n");
	printf("*************************\n");
}
void game()
{
	//扫雷游戏首先需要2个二维数组分别来存放布雷数据和排雷数据
	char mine[ROWS][COLS] = {0};
	char show[ROWS][COLS] = {0};
	//初始化,未布雷前mine数组全填'0',未排雷前show数组全填'*'表示神秘
	init_board(mine,ROWS,COLS,'0');
	init_board(show,ROWS,COLS,'*');
	//display_board(mine, ROW, COL);//遍历
	//开始布雷,随机在mine数组ROW行COL列范围内布雷COUNT个,雷用'1'表示
	lay_mines(mine, ROW, COL);
	display_board(mine, ROW, COL);//遍历
	display_board(show, ROW, COL);//遍历
	//排雷,输入坐标
	find_mine(mine,show,ROW,COL);
	

}
int main()
{
	//设置随机数生成起点
	srand((unsigned int)time(NULL));
	int input = 0;
	//游戏至少执行一次,所以用do while循环
	do
	{
		//显示游戏菜单
		menu();
		//请选择是否玩游戏
		printf("请选择是否进入游戏:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
		{
			printf("进入游戏\n");
			game();
			break;
		}
		case 0:
		{
			printf("退出游戏\n");
			break;
		}
		default:
		{
			printf("输入的数字有误,请重新选择\n");
			break;
		}
		}
	} while (input);
	return 0;
}

game2.c文件

#define _CRT_SECURE_NO_WARNINGS 1
#include "game2.h"

void init_board(char board[ROWS][COLS], int rows, int cols,char ch)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			board[i][j] = ch;
		}
	}
}

void display_board(char board[ROWS][COLS], int row, int col)
{
	printf("------扫雷游戏-----\n");
	int i = 0;
	int j = 0;
	for (j = 0; j <= col; j++)
	{
		//打印列号
		printf("%d ", j);
	}
	printf("\n");

	for (i = 1; i <= row; i++)
	{
		//打印行号
		printf("%d ", i);
		for (j = 1; j <= col; j++)
		{
			printf("%c ",board[i][j]);
		}
		printf("\n");
	}
	printf("------扫雷游戏-----\n");
}

void lay_mines(char board[ROWS][COLS], int row, int col)
{
	int count = COUNT;
	while (count)
	{
		//随机在mine数组ROW行COL列范围内布雷COUNT个,雷用'1'表示
		//获取随机坐标,范围(1,1)-(ROW,COL)
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		//防止同一坐标重复布雷
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			count--;
		}
	}
}

int around_mine_count(char board[ROWS][COLS], int x, int y)
{
	return (board[x - 1][y - 1] + board[x - 1][y] + board[x - 1][y + 1] +
			board[x][y - 1] + board[x][y + 1] +
			board[x + 1][y - 1] + board[x + 1][y] + board[x + 1][y + 1]) - 8 *'0';
}

//写个展开一片功能函数
//void unfold(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y,int count)
//{
//	if (count == 0)
//	{
//		int i = 0;
//		for (i = x - 1; i <= x + 1; i++)
//		{
//			int j = 0;
//			for (j = y - 1; j <= y + 1; j++)
//			{
//			count =	around_mine_count(mine, i, j);
//				if (show[i][j] == '*')
//				{
//					unfold(mine, show, i - 1, j - 1,count);
//				}
//			}
//		}
//	}
//	
//}
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	//输入排查坐标
	int x = 0;
	int y = 0;
	int win = 0;
	while (win= 1 && x <= row && y >= 1 && y <= col)
		{
			//检查是否重复排查相同坐标
			if (show[x][y] != '*')
			{
				printf("请勿重复排查同一坐标\n");
			}
			else
			{
				if (mine[x][y] == '1')//如果是雷
				{
					printf("很遗憾你被炸死了\n");
					display_board(mine, ROW, COL);//遍历
					break;
				}
				else//如果不是雷
				{
					//显示周围8个坐标里有多少个雷'1',写个函数
					int count = around_mine_count(mine, x, y);
					//显示到show数组对应坐标处,但应该转换成字符类型,比如周围有3个雷,int 3转换成'3','0'和'3'相差3,所以+'0'就行
					show[x][y] = count + '0';
					//展开一片
					//unfold(mine, show, x, y,count);
					display_board(show, ROW, COL);//遍历
					win++;
				}
			}
		}
		else
		{
			printf("输入坐标非法,请重新输入");
		}
	}
	if (win == row * col - COUNT)
	{
		printf("恭喜你,排雷成功\n");
		display_board(mine, ROW, COL);//遍历

	}
}

//标记明确是雷的坐标
//展开一片的功能:该坐标不是雷;该坐标周围没有雷;该坐标没有被排查过,递归实现

game2.h文件

#pragma once
#include 
#include 
#include 


#define ROW 9
#define COL 9

#define ROWS ROW+2
#define COLS COL+2

#define COUNT 10

void init_board(char board[ROWS][COLS], int rows, int cols,char ch);
void display_board(char board[ROWS][COLS], int row, int col);
void lay_mines(char board[ROWS][COLS], int row, int col);
void find_mine(char mine[ROWS][COLS],char show[ROWS][COLS],int row, int col);

练习:交换2个数

方法1:引入临时变量

    int tmp = a;
    a = b;
    b= tmp

方法2:通过算术操作符

此方法容易造成溢出,因为int是有表示范围的,若a,b都非常大,接近int能表示的最大数,那么a+b很可能就超过了int能表示的最大数了,所以不推荐

int a=10;
int b=20;
a=a+b;
b=a-b;
a=a-b;

方法3:通过异或操作符

此方法由于异或不会造成进位,所以a^b不可能超过int的表数范围,所以不会造成溢出,但是可读性不高,且仅仅适用于整数

#include 
int main()
{
 int a = 10;
 int b = 20;
 a = a^b;
 b = a^b;
 a = a^b;
 printf("a = %d b = %d\n", a, b);
 return 0;
}

总结:

三种方法最推荐的还是第一种

练习:自己实现一个求字符串长度的my_strlen()函数

方法1:循环

#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
//自己实现一个求字符串长度的my_strlen()函数
int my_strlen(const char* str)//const防止str指向的对象在此函数内部被修改
{
	int count = 0;
    assert(str);//断言,防止传参传空指针
	while (*str != '\0')
	{
		count++;
		str++;
	}
	return count;
}
int main()
{
	char arr[] = "abcde";
	printf("%d\n",my_strlen(arr));
	return 0;
}

方法2:递归

int my_strlen(char* str)
{
	if (*str == '\0')
	{
		return 0;
	}
	else
	{
		return 1 + my_strlen(str + 1);
	}
}
int main()
{
	char arr[] = "abcde";
	printf("%d\n",my_strlen(arr));
	return 0;
}

方法3:指针-指针

int my_strlen(char* str)
{
	char* start = str;
	while (*str != '\0')
	{
		str++;
	}
	return str - start;
}

int main()
{
	char arr[] = "abcde";
	printf("%d\n",my_strlen(arr));
	return 0;
}

练习:输入一个整数 a ,输出该数32位二进制表示中1的个数。其中负数用补码表示。

方法1:求商取余法

思考如何获取整数二进制补码中的每一位

十进制数获取每一位是a%10; a/10%10; a/10/10%10......直至取余前的商为0,余数反转即可

同理二进制数获取每一位是a%2; a/2%2; a/2/2%2......直至取余前的商为0,余数反转即可

C语言初阶练习题_第6张图片

int Count1(unsigned int a)
{
	int count = 0;
	while (a)
	{
		if ((a % 2) == 1)
		{
			count++;
		}
		a = a / 2;
	}
	return count;
}
int main()
{
	int a = -1;
	printf("%d\n",Count1(a));
	return 0;
}

注意形参必须是unsigned int ,接收负数时会把它看成一个很大的正数,因为无符号整型的二进制位是没有符号位的,最高位不再是符号位,这样才可以统计负数的补码中1的个数,否则无法统计

方法2:位运算和移位运算

a|1如果等于a本身,就说明a的二进制最低位是1,再右移1,循环进行比较

或a&1如果等于1,也说明a的二进制最低位是1,再右移1,循环进行比较

a&1获取的是a本身的二进制最低位,配合a>>i,i++.就能获得a的二进制的每一位

int Count1(int a)
{
	int count = 0;
	int i = 0;
	/*while (i < 32)
	{
		if ((a | 1) == a)
		{
			count++;
		}
		a >>= 1;
		i++;
	}*/
//或
	for (i = 0; i < 32; i++)
	{
		if (((a >> i) & 1) == 1)
		{
			count++;
		}
	}
	return count;
}

方法3:位运算

思考a=a&(a-1)会发生什么现象

C语言初阶练习题_第7张图片

现象就是每次都会去掉a二进制位中的一个1,直至a为0,执行了几次即有几个1

int Count1(int a)
{
	int count = 0;
	while (a)
	{
		a = a & (a - 1);
		count++;
	}
	return count;
}

n=n&(n-1)还有很多用途。比如判断一个数是不是2的n次方

C语言初阶练习题_第8张图片

我们观察到,如果一个数是2的n次方,则其二进制位中只有1个1,即上面代码的count=1

总结:获取整数二进制位每一位的方法

方法1:a%2; a/2%2; a/2/2%2......直至取余前的商为0,余数反转即可

方法2:a&1获取的是a本身的二进制最低位,配合a>>i,i++.就能获得a的二进制的每一位

练习:打印带空格直角三角形

输入描述:

多组输入,一个整数(2~20),表示直角三角形直角边的长度,即“*”的数量,也表示输出行数。

输出描述:

针对每行输入,输出用“*”组成的对应长度的直角三角形,每个“*”后面有一个空格

示例

输入:

4

输出:

      * 
    * * 
  * * * 
* * * *

方法1:分组打印

观察图案发现图案是个正方形,"  "空格随着行号增加不断减少,"* "随着行号增加不断增加,一行上控制打印"  "和"* "的变量是相反变化的,分开打印''  "和"* "

#include 
int main()
{
    int n = 0;
    int i = 0;
    int j = 0;
    int k = 0;
    int p = 0;
    while (scanf("%d", &n) == 1)//多组输入
    {
        for (i = 0, p = n-1 ; i < n; i++, p--)
        {
            for (k = 0; k < p; k++)
            {
                printf("  ");
            }
            for (j = 0; j <= i; j++)
            {
                printf("* ");
            }
            printf("\n");
        }
    }
    return 0;
}

方法2:判断打印

观察图案发现规律,一行上(行号+列号)

#include 
int main()
{
    int n = 0;
    int i = 0;
    int j = 0;
    while (scanf("%d", &n) == 1)
    {
        for (i = 0 ; i < n; i++)
        {
            //方法2:判断打印
            for (j = 0; j < n; j++)
            {
                if (i + j < n - 1)
                {
                    printf("  ");
                }
                else
                {
                    printf("* ");
                }
            }
            printf("\n");
        }
    }
    return 0;
}

练习:打印菱形

#include 
int main()
{
	int line = 0;
	scanf("%d", &line);
	int i = 0;
	int j = 0;
	//先打印上半部分
	for (i = 0; i < line; i++)
	{
		//打印一行
		//先打印空格
		for (j = 0; j 

总结:

打印图案的题,基本都是由*和空格组成的正方形,双层for循环嵌套打印 ,i控制行,j控制列

观察i和j之间的关系,找到规律即可。找i和j之间的规律关系时最好在画图工具里写出来i和j每次的取值,更容易找出规律,不要纯靠脑补​​​​​​​。

练习:打印杨辉三角 

#include 
int main() 
{
	int i = 0;
	int j = 0;
	int arr[10][10] = { 0 };
	for (i = 0; i < 10; i++)
	{
		for (j = 0; j < i; j++)
		{
			if (i == j || j == 0)
			{
				arr[i][j] = 1;
			}
			if(i >=2 && j >= 1)
			{
				arr[i][j] = arr[i - 1][j - 1] + arr[i - 1][j];
			}
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

练习:根据条件求比赛名次

C语言初阶练习题_第9张图片

#include 
int main()
{
	int a = 0;
	int b = 0;
	int c = 0;
	int d = 0;
	int e = 0;
	for (a = 1; a <= 5; a++)
	{
		for (b = 1; b <= 5; b++)
		{
			for (c = 1; c <= 5; c++)
			{
				for (d = 1; d <= 5; d++)
				{
					for (e = 1; e <= 5; e++)
					{    //名次说对一半的条件判断
						/*if (((a == 3 || b == 2) && !(a == 3 && b == 2)) &&
							((b == 2 || e == 4) && !(b == 2 && e == 4)) &&
							((c == 1 || d == 2) && !(c == 1 && d == 2)) &&
							((c == 5 || d == 3) && !(c == 5 && d == 3)) &&
							((e == 4 || a == 1) && !(e == 4 && a == 1)))*/
						if( (a == 3) + (b == 2)==1  &&
							(b == 2) + (e == 4)==1  &&
							(c == 1) + (d == 2)==1  &&
							(c == 5) + (d == 3)==1  &&
							(e == 4) + (a == 1)==1 )
						{
							if((a*b*c*d*e==120)&&(a+b+c+d+e==15))//剔除相同名次的情形
							printf("%d %d %d %d %d\n", a, b, c, d, e);	
						}
					}
				}
			}
		}
	}
	return 0;
}

总结:

穷举排列组合的所有可能的方法:循环嵌套

要穷举比赛名次所有的可能性,需要5层循环嵌套使用,如果不嵌套则做不到穷举 ,因为如果分开使用循环,排列组合的元素每次循环是没有关联的

多条判断语句的结果可相加来判断有其中有几条判断语句为真

 练习:根据供词和条件找真凶

C语言初阶练习题_第10张图片

方法1:穷举4个嫌疑人的可能性

#include 
//根据供词找真凶
int main()
{
	int a = 0;
	int b = 0;
	int c = 0;
	int d = 0;
	//用1表示真凶
	for (a = 0; a <= 1; a++)
	{
		for (b = 0; b <= 1; b++)
		{
			for (c = 0; c <= 1; c++)
			{
				for (d = 0; d <= 1; d++)
				{//供词和已知条件
					if ((a == 0) +
						(b == 0 && c == 1) +
						(c == 0 && d == 1) +
						(d == 0) == 3)
					{
						printf("%d %d %d %d\n", a, b, c, d);
					}
				}
			}
		}
	}
	return 0;
}

方法2:穷举凶手的可能性

int main()
{
	//方法2:只穷举凶手的可能性
	char killer = 'a';
	for (killer = 'a'; killer <= 'd'; killer++)
	{
		if ((killer != 'a') + (killer == 'c') + (killer == 'd') + (killer != 'd') == 3)
		{
			printf("%c\n", killer);
		}
	}
	return 0;
}

练习:查找杨氏矩阵内某元素

C语言初阶练习题_第11张图片

时间复杂度小于O(N)意思是不能通过遍历来查找,遍历查找的时间复杂度就是O(N)

查找杨氏矩阵内某元素k,若找到则返回1及其下标,找不到返回0和(-1,-1)

分析:通过观察杨氏矩阵就是每行元素从左往右都在增大,每列从上往下都在增大,右上角的元素比较特殊,拿要查找的数k和数组右上角的数进行对比,k小,则证明此列中没有k,则到前一列去找,k大,则证明此行中没有k,则到下一行去找。

要想一次性返回下标2个数,有2种方法:

方法1:返回型参数

传参时传行列数a,b的地址,通过传址调用改变a,b的值(改成要查找元素k的下标),最后打印a,b即可

//查找杨氏矩阵内某元素k,若找到则返回1及其下标,找不到返回0和(-1,-1)
#include 
int find_num(int arr[3][3], int* px, int* py, int k)
{
	//拿要查找的数k和数组右上角的数进行对比,k小,则证明此列中没有k,k大,则证明此行中没有k
	int x = 0; //右上角的数的下标
	int y = *py - 1;//右上角的数的下标
	while (x <= *px - 1 && y >= 0)
	{
		if (k < arr[x][y])
		{
			y--;//此列中没有k,则到前一行再查找
		}
		else if (k > arr[x][y])
		{
			x++;//此行中没有k,则到下一行再查找
		}
		else
		{
			*px = x;
			*py = y;
			return 1;//找到了k
		}
	}
	*px = -1;
	*py = -1;
	return 0;//找不到k
}
int main()
{
	int arr[3][3] = { 1,2,3,4,5,6,7,8,9 };
	int a = 3;//行数
	int b = 3;//列数
	int k = 0;
	scanf("%d", &k);
	int ret=find_num(arr, &a, &b, k);
	if (ret == 1)
	{
		printf("找到了%d,%d", a, b);
	}
	else
	{
		printf("找不到%d,%d",a,b);
	}
	return 0;
}

方法2:返回值设置为结构体类型

创建下标结构体类型,成员变量为下标,返回值类型设置成此结构体类型,创建结构体对象,通过   .   操作符把成员变量赋值成要查找元素k的下标,最后返回即可

#include 
struct coordinate {//创建结构体类型
	int x;
	int y;
};
struct coordinate find_num(int arr[3][3], int a, int b, int k)
{
	struct coordinate c = { -1,-1 };//创建结构体类型变量c
	//拿要查找的数k和数组右上角的数进行对比,k小,则证明此列中没有k,k大,则证明此行中没有k
	int x = 0; //右上角的数的下标
	int y = b - 1;//右上角的数的下标
	while (x <= a - 1 && y >= 0)
	{
		if (k < arr[x][y])
		{
			y--;//此列中没有k,则到前一行再查找
		}
		else if (k > arr[x][y])
		{
			x++;//此行中没有k,则到下一行再查找
		}
		else
		{
			c.x = x;
			c.y = y;//重新赋值
			return c;//找到了,返回结构体变量c
		}
	}
	return c;//找不到,返回未更改的结构体变量c
}
int main()
{
	int arr[3][3] = { 1,2,3,4,5,6,7,8,9 };
	int a = 3;//行数
	int b = 3;//列数
	int k = 0;
	scanf("%d", &k);
	struct coordinate ret = find_num(arr, a, b, k);//直接传行列数
	printf("%d,%d", ret.x, ret.y);
	return 0;
}

你可能感兴趣的:(c++,算法,开发语言,1024程序员节)