C语言基础 Day08 字符串和内存管理

1. 字符串

1.1 字符串的一些案例

【求字符串非空格元素个数】:给定一个字符串,求该字符串中非空格字符元素的个数。

#if 0

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>


int no_space_str(char *str)
{
	int count = 0;

	while (*str)
	{
		if (*str != ' ')
			count++;
		str++;
	}
	return count;
}


int main(int argc, char* argv[])
{
	char str[] = "ni hao ya xiao peng you";

	int ret = no_space_str(str);

	printf("ret = %d\n", ret);

	system("pause");
	return 0;
}

#endif

【回文字符串】:给定一个字符串,判断字符串是否为一个回文字符串。

#if 0

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>

// 判断字符串是否是回文
int str_palindrome(char *str)
{
	char *start = str;
	char *end = str + strlen(str) - 1;

	while (start < end)
	{
		if (*start != *end)
			return 0;
		start++;
		end--;
	}

	return 1;
}

int main(int argc, char* argv[])
{
	char str[] = "lolmlol";

	int ret = str_palindrome(str);

	if (ret)
		printf("是回文\n");
	else
		printf("不是回文\n");

	system("pause");
	return 0;
}

#endif

【字符串逆序】:给定一个字符串,输出该字符串元素逆序的字符串。

#if 0

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>

// 字符串逆序
void str_inverse(char *str)
{
	char *start = str;						// 记录首元素地址
	char *end = str + strlen(str) - 1;		// 记录最后一个元素地址

	while (start < end)						// 首元素地址是否小于最后一个元素地址
	{
		char tmp = *start;
		*start = *end;
		*end = tmp;
		start++;							// 首元素对应指针后移
		end--;								// 尾元素对应指针前移
	}
}

int main(int argc, char* argv[])
{
	char str[] = "ni hao sao ya ha ha ha ha ha";

	str_inverse(str);

	printf("%s\n", str);

	system("pause");
	return 0;
}

#endif

1.2 字符串的操作函数

函数名函数原型返回值参数作用tip
strcpychar *strcpy(char *dest, const char *src);拷贝完成dest的首地址dest:拷贝目标内存空间
src:原字符串
将src的内容拷贝给dest需要保证dest的空间足够大,不安全
strncpychar *strncpy(char *dest, const char *src, size_t n);拷贝完成dest的首地址dest:拷贝目标内存空间
src:原字符串
n:拷贝的大小
将src的内容拷贝给dest,只拷贝n个字节,通常n与dest对应的空间一致n > strlen(src)时,只拷贝src的大小
n < strlen时,只拷贝n字节的大小,末尾不添加’\0’
strcatchar *strcat(char *dest, const char *src);拼接完成dest的首地址dest:被拼接的字符串
src:待拼接的字符串
将src的内容拼接到dest之后需要保证dest空间足够大,不安全
strncatchar *strncat(char *dest, const char *src, size_t n);拼接完成dest的首地址dest:被拼接的字符串
src:待拼接的字符串
n:拼接的大小
将src的前n个字符拼接到dest后,形成一个新的字符串-
strcmpint strcmp(const char *s1, const char *s2);返回一个整数,-1表示s1小于s2,0表示两字符串相等,1表示s1大于s2s1:待比较的第一个字符串
s2:待比较的第二个字符串
比较s1和s2的大小逐位比较每一位字符的ASCII码的大小,而不是比较字符串ASCII码之和
strncmpint strncmp(const char *s1, const char *s2, size_t n);返回一个整数,-1表示s1小于s2,0表示两字符串相等,1表示s1大于s2s1:待比较的第一个字符串
s2:待比较的第二个字符串
n:比较的位数
逐位比较两个字符串的前n个字符-
sscanfint sscanf(const char *str, const char *format, …);-str:读入的字符串
format:读入的格式字符串
从字符串str中获取输入,按照format格式的形式-
sprintfint sprintf(char *str, const char *format, …);-str:写出的空间
format:写出的格式字符串
将字符串按照格式写入指定的内存空间中-
strchrchar *strchr(const char *s, int c);第一次出现的位置,未找到为NULLs:字符串
c:需要查找的字符
在字符串s中查找指定字符c出现的位置-
strrchrchar *strrchr(const char *s, int c);第一次出现的位置,未找到为NULLs:字符串
c:需要查找的字符
在字符串中从右往左查找指定字符c出现的位置-
strstrchar *strstr(const char *str, const char *substr);第一次出现的位置,未找到为NULLstr:字符串
substr:需要查找的子字符串
在字符串str中查找substr字符串出现的位置-
strtokchar *strtok(char *str, const char *delim);字符串拆分后的首地址str:需要拆分的字符串
delim:拆分的分割字符串
用分割字符串中的字符去分割字符串str分割是按照delim的每个字符进行分割,而不是整个字符串分割。该函数的操作拆分是在原字符串上进行的,所以需要原字符串可读可写
atoiint atoi(const char *nptr);int类型的整数nptr:需要转换的字符串将字符串转换为int类型整数-
atollong atol(const char *nptr);long类型的整数nptr:需要转化的字符串将字符串转换为long类型的整数-
atoffloat atof(const char *nptr);float类型的浮点数nptr:需要转换的字符串将字符串转换为float类型的浮点数-

下面看一下关于上述字符串操作函数的一些使用案例。

字符串拷贝函数strcpy和strncpy的示例如下:

#if 0

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>


int main(int argc, char* argv[])
{
	char s1[] = "I love you, you love me, mi xue bing cheng tian mi mi";
	char s2[100] = { 0 };
	char s3[100] = { 0 };

	char *p = strcpy(s2, s1);

	printf("s1 = %s\n", s1);
	printf("------------strcpy-----------\n");
	printf("p = %s\n", p);
	printf("s2 = %s\n", s2);


	p = strncpy(s3, s1, 10);
	printf("-------------strncpy-------------\n");
	printf("p = %s\n", p);
	printf("s3 = %s\n", s3);


	p = strncpy(s3, s1, 1000);
	printf("p = %s\n", p);
	printf("s3 = %s\n", s3);

	system("pause");
	return 0;
}

#endif

字符串拼接函数strcat和strncat的示例代码如下:

#if 0

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>


int main(int argc, char* argv[])
{
	char s1[100] = "hello ";
	char s2[] = "world";

	char *p = strcat(s1, s2);
	
	printf("==================strcat======================\n");
	printf("p = %s\n", p);
	printf("s1 = %s\n", s1);

	char s3[100] = "nihao! ";
	p = strncat(s3, s2, 2);

	printf("==================strncat======================\n");
	printf("p = %s\n", p);
	printf("s3 = %s\n", s3);

	system("pause");
	return 0;
}

#endif

字符串比较函数strcmp和strncmp的示例代码如下:

#if 0

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>


int main(int argc, char* argv[])
{
	char *s1 = "hello";
	char *s2 = "helloworld";

	int ret = strcmp(s1, s2);

	printf("============strcmp===============\n");
	printf("ret = %d\n", ret);

	ret = strncmp(s1, s2, 5);

	printf("=============strncmp=============\n");
	printf("ret = %d\n", ret);

	system("pause");
	return 0;
}

#endif

字符串格式输入输出函数sscanf和sprintf的示例代码如下:

#if 0

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>


int main(int argc, char* argv[])
{
	char s[100] = { 0 };

	int a, b, c;

	sprintf(s, "%d + %d = %d", 10, 20, 30);
	puts(s);

	sscanf(s, "%d + %d = %d", &a, &b, &c);
	printf("a = %d\n", a);
	printf("b = %d\n", b);
	printf("c = %d\n", c);

	system("pause");
	return 0;
}

#endif

字符串查找函数strchr、strrchr和strstr函数示例代码如下:

#if 0

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>


int main(int argc, char* argv[])
{
	// 从左到右在字符串中查找字符
	printf("%s\n", strchr("ahahahaheiheihei", 'e'));

	// 从右往左在字符串中查找字符
	printf("%s\n", strrchr("xixihahahohoho", 'a'));

	// 字符串中查找字符串
	printf("%s\n", strstr("xixhahahohoahahahah", "haha"));

	system("pause");
	return 0;
}

#endif

字符串分割函数strtok示例代码如下:

#if 0

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>


int main(int argc, char* argv[])
{
	char s1[] = "www.iloveu.fit";

	char *p = strtok(s1, ".");

	while (p)
	{
		printf("%s\n", p);
		p = strtok(NULL, ".");
	}

	char s2[] = "https://www.iloveu.fit:8080";

	p = strtok(s2, ":/.");

	while (p)
	{
		printf("p = %s\n", p);
		p = strtok(NULL, ":/.");
	}

	system("pause");
	return 0;
}

#endif

字符串转换函数atoi、atol和atof的示例代码如下:

#if 0

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>


int main(int argc, char* argv[])
{

	int inumber = atoi("12");
	float fnumber = atof("520.1314f");
	long lnumber = atol("1314L");

	printf("inumber = %d\n", inumber);
	printf("lnumber = %ld\n", lnumber);
	printf("fnumber = %f\n", fnumber);

	system("pause");
	return 0;
}

#endif

2. 内存管理

2.1 作用域

C语言变量的作用域一般分为代码块作用域(代码块是在{}之间的代码)、函数作用域、文件作用域三类。

定义在函数内部的变量就是局部变量,局部变量又叫auto自动变量,其中auto可写可不写。一般情况下在代码块内部定义的变量都是自动变量。在没有给它进行赋值操作的时候,其值是随机的。该变量的作用域从定义的位置开始,到包裹该变量的第一个左大括号配对的右大括号处结束。

如果在变量的定义前加上static关键字,那么此时的变量就是静态局部变量。静态局部变量的作用域也是在函数的内部有效。但是static局部变量只初始化一次,但是可以多次进行赋值。若static局部变量未赋值时,系统自动赋值赋值为0。不过静态局部变量存在全局位置,通常用做计数器来使用。

定义在函数外部的变量叫全局变量。全局变量的作用域从定义位置开始,默认到本文件结束。如果其它文件想要使用,可以通过申明的方式将作用域导出。因此不同文件的不可以重名。

若在全局变量前也加上static关键字,则该全局变量为静态全局变量。静态全局变量的作用域被限制在本文件内部,不允许通过申明导出到其它文件。因此不同的文件中可以重名,作用域不冲突。静态全局变量也只初始化一次。

函数也分静态函数和全局函数。在C语言中,函数默认都是全局的,但是可以使用static关键字将函数声明为静态。使用了static关键字之后,该函数就只能在定义了这个函数的文件中使用,其它文件不能调用,即使申明了也不能用。因此不同文件中的static函数是可以重名的。同理如果是全局函数,则不可以重名。

下面给出变量和函数的作用域和生命周期:

类型作用域生命周期
auto变量一对{}内当前函数
static局部变量一对{}内整个程序运行期
extern变量整个程序整个程序运行期
static全局变量当前文件整个程序运行期
extern函数整个程序整个程序运行期
static函数当前文件整个程序运行期
register变量一对{}内当前函数
全局变量整个程序整个程序运行期

下面来看一个关于局部变量的示例。

demo1.c文件:

#include <stdio.h>

static int a = 5201314;

void test(void)
{
	static int b = 0;

	printf("b = %d\n", b ++);
}

demo2.c文件内:

#if 0

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>

void test(void);	// 全局函数申明

int m = 4456;

int main(int argc, char* argv[])
{
	int i = 1212;
	for (int i = 0; i < 10; i++)
	{
		printf("i = %d\n", i);
		test();
	}

	printf("i = %d\n", i);

	system("pause");
	return 0;
}

#endif

2.2 内存布局

C语言程序在运行的时候,操作系统会进行分区,分别是代码段、数据段、栈区、堆区。

代码段(.text)存放的是源程序的二进制代码,不可以修改,是只读的。

数据段细分为.data段、.bss段、.rodata段。其中.data段主要存放初始化为非0的全局变量和静态变量。.bss段存放初始化为0的全局变量和静态变量,以及未初始化的全局变量和静态变量。程序加载执行前,会将该段的整体赋值为0。.rodata段是只读数据段,存放常量。也有人把数据段称为静态区或者全局区。

栈区(stack)主要是编译器自动分配和释放。栈是一种后进先出(LIFO)的结构。主要存放函数的参数值、返回值、局部变量等。前面函数调用会产生栈帧,栈帧就是位于栈上的。局部变量的生命周期就是在栈上申请空间到释放空间的期间。

堆区(heap)容量远远比栈区要大,一般由程序员进行分配和释放的。若程序员不释放空间,则是程序结束时候由操作系统系统回收。

不过windows的分区如下:

在这里插入图片描述

由于Windows源代码不开放,所以根据大量程序的运行,可以得出主要分为上述四个区,具体的细节无法得知。不过在Windows中系统分配给程序的栈空间大小默认是1M,通过其它方式一般可以扩大到8M。

关于Linux的分区如下:

在这里插入图片描述
由于Linux的源码是开放的,所以可以得到上述详细的分区结构。不过Linux中系统给每个程序分配的栈空间默认是8M,通过其它方式可以扩大到16M。

若对比其它高级语言,我们也可以发现其实只有C语言是把数组放在栈上。这也是C语言中数组的长度为什么是一个常量,而且定义了就不能动态扩容的一个原因。若数组在堆上,则可以动态扩容。在编写程序的时候,我们一般把占用空间小的数据放在栈上,而占用空间大的则直接放在堆区。比如int a[30]; 就可以放在栈上,也可以放在堆上;而int b[1000000];我们则放在堆上,不放在栈上。

对于内存的操作,我们有以下一些函数:

函数名函数原型函数返回值函数参数函数作用tip
memsetvoid *memset(void *s, int c, size_t n);s的首地址s: 需要操作内存s的首地址
c: 填充的字符,范围为0~255
n: 指定需要设置的大小
将s的内存区域前n个字节以参数c填入-
memcpyvoid *memcpy(void *dest, const void *src, size_t n);dest的首地址dest: 目的内存首地址
src: 源内存首地址
n: 需要拷贝的字节数
拷贝src所指的内存内容的前n个字节到dest所指的内存地址上dest和src所指的内存空间不可重叠,否则可能会导致程序报错。有重叠情况请用memmove函数
memmovevoid *memmove(void *dest, const void *src, size_t n);dest的首地址dest: 目的内存首地址
src: 源内存首地址
n: 需要拷贝的字节数
拷贝src所指的内存内容的前n个字节到dest所指的内存地址上dest和src的内存空间可以重叠
memcmpint memcmp(const void *s1, const void *s2, size_t n);整数,若等于0则前n个字节内容相等,若大于0说明s1的前n个字节内容大于s2,若小于0说明s1的前n个字节内容小于s2s1: 内存首地址1
s2: 内存首地址2
n: 须比较的前n个字节
比较s1和s2所指向内存区域的n个字节-

关于内存操作的函数示例代码如下:

#if 0

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>

// memset函数:以字节为单位初始化
void test1()
{
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 8, 10 };
	
	for (int i = 0; i < 10; i++)
		printf("%d ", arr[i]);
	putchar('\n');

	memset(arr, 0, sizeof(arr));

	for (int i = 0; i < 10; i++)
		printf("%d ", arr[i]);
	putchar('\n');

	char *chs = malloc(sizeof(char) * 10);
	memset(chs, 'a', 10);

	for (int i = 0; i < 10; i++)
		printf("%c ", chs[i]);
	putchar('\n');

	free(chs);
	chs = NULL;

}

// memcpy函数,内存拷贝,但是拷贝的空间和待考贝的空间不能有重叠
void test2()
{
	int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
	int *p = malloc(sizeof(int)* 10);

	for (int i = 0; i < 10; i++)
		printf("%d ", p[i]);
	putchar('\n');

	memcpy(p, arr, sizeof(arr));

	for (int i = 0; i < 10; i++)
		printf("%d ", p[i]);
	putchar('\n');

	free(p);
	p = NULL;

	for (int i = 0; i < 10; i++)
		printf("%d ", arr[i]);
	putchar('\n');

	// 内存空间发生重叠
	memcpy(&arr[3], arr, 20);
	for (int i = 0; i < 10; i++)
		printf("%d ", arr[i]);
	putchar('\n');
	
}

// memmove函数,作用于memcpy一样,但是可以用于拷贝空间和被拷贝空间重叠的情况
void test3()
{
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	
	for (int i = 0; i < 10; i++)
		printf("%d ", arr[i]);
	putchar('\n');

	memmove(arr + 2, arr, 24);

	for (int i = 0; i < 10; i++)
		printf("%d ", arr[i]);
	putchar('\n');
}

// memcmp函数,用于比较内存空间的内容是否一样,不限数据类型
void test4()
{
	int arr1[] = { 1, 2, 3, 4, 5 };
	int arr2[] = { 1, 2, 3, 4, 5 };
	int arr3[] = { 1, 2, 3, 5, 6 };

	if (memcmp(arr1, arr2, sizeof(arr1)))
		printf("arr1和arr2的内容不一样\n");
	else
		printf("arr1和arr2的内容一样\n");

	if (memcmp(arr1, arr3, sizeof(arr1)))
		printf("arr1和arr3的内容不一样\n");
	else
		printf("arr1和arr3的内容一样\n");

	if (memcmp(arr1, arr3, sizeof(int)* 3))
		printf("arr1和arr3的前三个元素不一样\n");
	else
		printf("arr1和arr3的前三个元素一样\n");

	
	char ch = 0xff;
	int a = 0xff;

	if (memcmp(&ch, &a, sizeof(char)))
		printf("ch与a的内容不一样\n");
	else
		printf("ch与a的内容一样\n");


	ch = 0xffff;
	a = 0xffff;

	if (memcmp(&ch, &a, 2))
		printf("ch和a的内容不一样\n");
	else
		printf("ch和a的内容一样\n");

}

int main(int argc, char* argv[])
{
	//test1();
	//test2();
	//test3();
	test4();

	system("pause");
	return 0;
}

#endif

除了上述的操作之外,我们可能还会在堆上申请和释放内存。申请和释放空间的函数如下:

函数名函数原型返回值参数作用tip
mallocvoid *malloc(size_t size);分配成功则返回分配空间的首地址,若失败返回NULLsize: 需要分配内存空间大小,单位是字节在内存的堆区分配一块长度为size字节的连续区域-
freevoid free(void *ptr);ptr: 需要释放空间的首地址,被释放区域应该是malloc函数所分配的区域释放ptr所指向的内存空间,ptr可以是任意类型的指针变量,指向被释放区域的首地址参数需要是malloc申请内存空间返回的首地址,否则会报错。且不能对同一内存空间进行多次释放

下面给出关于malloc和free的示例代码如下。

#if 0

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>


int main(int argc, char* argv[])
{
	srand(time(NULL));

	int k = 1000000;
	int *p = (int *)malloc(sizeof(int)* k);

	if (!p)
	{
		printf("malloc error!\n");
		return -1;
	}

	// 写入数据到malloc空间
	for (int i = 0; i < 10; i++)
	{
		p[i] = rand() % 100;
	}

	int *ptr = p;	// 需要改变指针的值重新赋值给其它指针,后面用于free不会出错

	for (int j = 0; j < 10; j++)
	{
		printf("p[%d] = %d\n", j, *ptr ++);
	}

	ptr = NULL;

	// 释放申请的内存
	free(p);
	p = NULL;

	system("pause");
	return 0;
}

#endif

在对堆上进行操作的时候需要注意的是如果对于多层内存进行申请和释放的时候。申请的时候一定要先申请外层的空间,再申请内层的空间。释放的时候要先释放内层的空间再释放外层的空间,示例如下。

#if 0

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>

#define N 5


int main(int argc, char* argv[])
{
	srand(time(NULL));

	int memsize[N] = { 0 }; // 记录每个指针开辟空间的大小

	for (int i = 0; i < N; i++)
		memsize[i] = rand() % 10 + 1;	// 每个空间大小为随机数 1~10个元素

	// 先开辟外层空间
	int **pptr = (int **)malloc(sizeof(int *) * 5);
	int **pp = pptr;
	// 开辟内层空间
	for (int i = 0; i < N; i++)
		pptr[i] = (int *)malloc(sizeof(int)* memsize[i]);

	// 对内存空间进行随机赋值
	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < memsize[i]; j++)
			pptr[i][j] = rand() % 100 + 1;
	}

	// 输出内存空间的值
	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < memsize[i]; j++)
		{
				printf("%d ", *(*(pp + i) + j));
		}
		putchar('\n');
	}

	pp = NULL;
	// 释放空间要先释放内层空间再释放外层空间
	for (int i = 0; i < N; i++)
	{
		free(pptr[i]);
		pptr = NULL;
	}
	free(pptr);

	system("pause");
	return 0;
}

#endif

3.综合案例

3.1 成绩排序

给定三个人的三门科目的成绩。对每个人的成绩按照从小到大的顺序进行排序,并且对每个人按照总分从小到大进行排序。要求存储三个学生的成绩的空间全部位于heap区。

思路:先开辟外层三个学生的指针,再分别对每个学生开辟存三个科目成绩空间的指针。排序的时候使用冒泡排序法。

#if 1

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>

// 给出三名学生三门课的成绩,根据成绩大小给成绩排序,根据总成绩给三名学生排序

#define N 3


// 求一个学生的三门课的成绩
int get_sum_grade(const int *std)
{
	int sum = 0;
	for (int i = 0; i < N; i++)
		sum += std[i];
	return sum;
}

// 对学生单科目成绩进行冒泡排序
void bubble_sort_course(int p[])
{
	for (int i = 0; i < N - 1; i++)
	{
		for (int j = 0; j < N - 1 - i; j++)
		{
			if (p[j] > p[j + 1])
			{
				int tmp = p[j];
				p[j] = p[j + 1];
				p[j + 1] = tmp;
			}
		}
	}
}

// 对学生的总成绩进行冒泡排序
void bubble_sort_std(const int *p[])
{
	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < N - i - 1; j++)
		{
			if (get_sum_grade(p[j]) > get_sum_grade(p[j + 1]))
			{
				int *tmp = p[j];
				p[j] = p[j + 1];
				p[j + 1] = tmp;
			}
		}
	}
}

// 显示学生的成绩表格
void show_stds(const int  * const p[], const int (*sum_grade)(int *))
{
	printf("============================================\n");
	printf("%-6s%-6s%-6s%-6s%-6s\n", "序号", "课程1", "课程2", "课程3", sum_grade ? "总分" : "");
	for (int i = 0; i < N; i++)
	{
		printf("%-6d", i + 1);
		for (int j = 0; j < N; j++)
		{
			printf("%-6d", p[i][j]);
		}

		if (sum_grade) 
			printf("%-6d", sum_grade(p[i]));
		putchar('\n');
	}
	printf("============================================\n");
}

int main(int argc, char* argv[])
{
	int **std = (int **)malloc(sizeof(int *)* N);
	for (int i = 0; i < N; i++)
		std[i] = (int *)malloc(sizeof(int)* 3);

	std[0][0] = 98;
	std[0][1] = 78;
	std[0][2] = 93;

	std[1][0] = 88;
	std[1][1] = 98;
	std[1][2] = 78;

	std[2][0] = 96;
	std[2][1] = 78;
	std[2][2] = 88;

	printf("排序前: \n");

	// 显示学生成绩
	show_stds(std, NULL);
	show_stds(std, get_sum_grade);

	// 排序
	printf("单科成绩排序后: \n");
	for (int i = 0; i < N; i++)
		bubble_sort_course(std[i]);
	show_stds(std, get_sum_grade);

	printf("总成绩排序后: \n");
	bubble_sort_std(std);
	show_stds(std, get_sum_grade);

	system("pause");
	return 0;
}

#endif

3.2 打字游戏

用控制台写一个打字游戏,在界面上显示一串小写字母,然后根据你的输入字符是否与第一个字符相同,若相同则消失第一个字符,若不相同则不消失直到输入正确才消失。在此期间统计输入正确的个数和输入错误的个数。并统计打字花费的时间。小写字母序列的长度和小写字母由系统随机生成。不要将所有代码都写在main函数中,要求适当封装函数。下面给出需要使用到的一些其它点。

输入无需按回车键:在Windows平台,WINAPI自带类似功能函数

	char ch = _getch(); //需要头文件#include <conio.h>

清屏函数:Windows平台可用system("cls");
随机数相关函数:

//所需头文件:
	#include <stdlib.h> //srand(), rand()
	#include <time.h> //time()

	srand((unsigned int)time(NULL)); //随机种子
	rand();	//随机数

计算耗时相关函数:

获取当前系统时间:
	time_t start_time = time(NULL); //需要头文件#include <time.h>

思路:使用随机数随机生成0~25的字符,并加上’a’即可得到随机的26个小写字母,使用循环连续生成可以得到多个字母。在打字之前获取一次系统时间。字符串若没有到字符串末尾则用循环一直循环获取字符,每打对一次字母就将字符串向后移动一个字节,清屏一次再显示。直到字符串末尾。最后打字完成后再获取一次系统时间。两次时间差即是打字话费的时间。

#if 1

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <Windows.h>
#include <conio.h>

int len;

// 获取要打字的字符串
char *generate_str()
{
	len = rand() % 20 + 1;
	char *str = (char *)malloc(sizeof(char)* (len + 1));
	
	memset(str, 0, len + 1);

	for (int i = 0; i < len; i++)
		str[i] = rand() % 26 + 97;

	return str;
}

// 显示时长
void show_res(const unsigned int start_time, const unsigned int end_time, const int right, const int wrong)
{
	unsigned int cost = end_time - start_time;
	unsigned int s = cost % 60;
	cost -= s;
	unsigned int min = cost / 60;
	printf("您花费了 %u min %u s \n", min, s);
	printf("总共 %d 个, 正确 %d 个, 错误 %d 个\n", len, right, wrong);
}

int main(int argc, char* argv[])
{
	srand(time(NULL));
	char *type_str = generate_str();

	int right = 0, wrong = 0;

	unsigned int start_time = time(NULL);
	char *ptype_str = type_str;
	char ch = 0;
	while (*ptype_str)
	{
		printf("%s\n", ptype_str);
		do
		{
			ch = _getch();
			if (ch != *ptype_str) wrong++;
		} while (ch != *ptype_str);
		system("cls");
		right++;
		ptype_str++;
	}
	unsigned int end_time = time(NULL);

	show_res(start_time, end_time, right, wrong);

	system("pause");
	return 0;
}

#endif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值