C语言经典88案例,我文科妹妹说她都学会了!

语言: CN / TW / HK

案例ex01: 将字符串转换为一个整数

1 题目

函数:fun()

功能:将字符串转换为一个整数

描述:

【不能使用C语言提供的字符串函数】

输入:字符串"-1234"

输出:整型 -1234

2 思路

思路:将字符串每个位置的字符进行对应的ASCII码转换

例如:字符 '0'-'9' 对应的十进制整数是48~57,那么,将对应的整数减去48就得到了对应的整数

二进制 十进制 十六进制 字符
0011 0000 48 30 0
0011 0001 49 31 1
0011 0010 50 32 2
0011 0011 51 33 3
0011 0100 52 34 4
0011 0101 53 35 5
0011 0110 54 36 6
0011 0111 55 37 7
0011 1000 56 38 8
0011 1001 57 39 9

3 代码

#include<stdio.h>
#include<string.h>

/**
函数:fun()
功能:将字符串转换为一个整数

描述:
【不能使用C语言提供的字符串函数】
输入:字符串"-1234"
输出:整型 -1234
**/

long fun(char *p) {
	int r = 0;		//数字个数
	long res = 0L;	// 转化后的数字
	int pos = 1;		// 位数*10 *100 ...
	int size = strlen(p); 	// 字符串长度
	if (p[0] == '-'){
		r = size - 1;
	} else {
		r = size;
	}
	// 从前往后转换的话,需要分情况去计算。即:不是负数的话从p[0] 和 如果是负数的话需要从p[1]开始
	// 所以,可以从后往前计算,循环次数是 r
	for (int i = 0; i < r; ++i) {
		res += (p[size-1-i]-48)*pos;
		pos *= 10;
	}
	return p[0]=='-'?-res:res;
}

int main(int argc, char const *argv[]) {
	char s[6];
	printf("Enter a string: ");
	gets(s);
	long res = fun(s);	
	printf("Convert Result: %ld\n", res);
	return 0;
}

示例结果:

$ gcc ex1.c -o demo
$ ./demo
Enter a string: -1234
Convert Result: -1234
$ ./demo
Enter a string: 9089
Convert Result: 9089

案例ex02: 将M行N列的二维数组中的字符数据,按列的顺序依次放到一个字符串中

1 题目

编写:fun()

功能:将M行N列的二维数组中的字符数据,按列的顺序依次放到一个字符串中

例如:

二维数组中的数据为:

W W W W

S S S S

H H H H

则字符串中的内容是:WSHWSHWSH

2 思路

第一层循环按照列数进行,第二层循环按照行数

然后依次提出每一列的字符

3 代码

为了熟悉二维数组的指针表示,部分代码给出了数组表示和指针表示

#include<stdio.h>
#include<string.h>
#define M 3
#define N 4
/**
编写:fun()
功能:将M行N列的二维数组中的字符数据,按列的顺序依次放到一个字符串中

例如:
二维数组中的数据为:
W W W W
S S S S
H H H H
则字符串中的内容是:WSHWSHWSH
**/

//   0 1 2 3
// 0 W W W W
// 1 S S S S
// 2 H H H H


char *fun(char s[M][N], char *res) {
	int t = 0;
	for (int i = 0; i < N; ++i) {
		for (int j = 0; j < M; ++j) {
			res[t++] = s[j][i];
      // res[t++] = *(*(a*i)+i);   // 指针表示
		}
	}
	res[t] = '\0';
	return res;
}


int main(int argc, char const *argv[]) {
	char a[M][N] = {'M', 'M', 'M', 'M', 'S', 'S', 'S', 'S', 'H', 'H', 'H', 'H'};
	int size_res = M*N;
	char res[size_res];
	printf("二维数组中元素:\n");
	for (int i = 0; i < M; ++i) {
		for (int j = 0; j < N; ++j) {
			printf("%c\t", a[i][j]);
      // printf("%c\t", *(*(a*i)+j));   // 指针表示
		}
		printf("\n");
	}
	
	printf("按列的顺序依次:\n%s\n", fun(a, res));
}

示例结果:

$ gcc ex002.c -o demo
$ ./demo
二维数组中元素:
M	M	M	M
S	S	S	S
H	H	H	H
按列的顺序依次:
MSHMSHMSHMSH

案例ex03: 统计一行字符串单词的个数,作为函数值返回

1 题目

函数:fun()

功能:统计一行字符串单词的个数,作为函数值返回

一行字符串在主函数中输入,规定所有单词都是由小写字母组成,单词之间由若干空格隔开,一行的开始没有空格

2 思路

逐个字符进行判断是否为空

下面“空开处”指的是一个空格或者若干空格隔开单词的说法

  1. 开头无空格,故需要判断结尾有没有空开出,如果有,直接计算空开出就是单词数,如果没有需要单词数加1
  2. 上述1中的空开出需要做处理,由于单词间由若干空格隔开,故判断一个空格的前一个是否为空格,如果不是,数量加1;如果是,不做处理

3 代码

#include<stdio.h>
#include<string.h>
#define M 3
#define N 4
/**
函数:fun()
功能:统计一行字符串单词的个数,作为函数值返回
		
一行字符串在主函数中输入,规定所有单词都是由小写字母组成,单词之间由若干空格隔开,一行的开始没有空格
**/

int fun(char *s) {
	int cnt = 0; 	// 单词个数
	int i = 0;
	while(*(s+i)!='\0') {
		if (*(s+i) == ' ' && *(s+i-1) != ' ') {
			cnt += 1;
		}
		++i;
	}
	if (*(s+i-1) != ' '){	// 如果单词结尾没有空格,则单词数需要空开数+1
		return cnt + 1;
	}
	return cnt;
}

int main(int argc, char const *argv[]) {
	char s[] = "hello world      i am c language";
	printf("字符串内容:%s\n", s);
	printf("单词个数为:%d\n", fun(s));
}

示例结果:

$ gcc ex003.c -o demo
$ ./demo
字符串内容:hello world      i am c language
单词个数为:6

案例ex04: 统计各个年龄阶段的人数

1 题目

函数:fun()

功能:统计各个年龄阶段的人数

描述:

N个年龄通过调用随机函数获得,并存放在主函数的age中

要求函数把0-9岁年龄段的放在d[0]中,把10-19岁年龄段的放在d[1]中,依次类推。把100岁及以上的放在d[10]中

结果在主函数中输出

2 思路

随机使用 rand() 函数,头文件为#include <stdlib.h>

rand()函数是按指定的顺序来产生整数,因此每次执行上面的语句都打印相同的两个值,所以说C语言的随机并不是真正意义上的随机,有时候也叫伪随机数,使用 rand() 生成随机数之前需要用随机发生器的初始化函数 srand(unsigned seed)(也位于 stdlib.h 中) 进行伪随机数序列初始化,seed 又叫随机种子,通俗讲就是,如果每次提供的 seed 是一样的话,最后每一轮生成的几个随机值也都是一样的,因此叫伪随机数,所以需要每次提供不同的 seed 达到完全的随机,我们通常用时间函数 time(NULL) 作为 seed ,因为时间值每秒都不同,但是在此题中使用不到time这个工具

3 代码

#include <stdio.h>
#include <stdlib.h>
#define M 11
#define N 100

/**
函数:fun()
功能:统计各个年龄阶段的人数
描述:
N个年龄通过调用随机函数获得,并存放在主函数的age中
要求函数把0-9岁年龄段的放在d[0]中,把10-19岁年龄段的放在d[1]中,依次类推。把100岁及以上的放在d[10]中
结果在主函数中输出
**/

void fun(int *age, int *d) {
	for (int i = 0; i < N; ++i) {
		if (*(age+i)<100) {
			d[(*(age+i))/10] += 1;
		} else {
			d[M-1] += 1;
		}
	}
}

int main(int argc, char const *argv[]) {
	int age[N];		// 100个用户
	int d[M]={0}; 		// 11个年龄段
	for (int i = 0; i < N; ++i) {
		*(age+i) = rand()%121;	// 设定年龄的范围是0-120
	}
	fun(age, d);
	printf("各年龄阶段人数数量:\n");
	for (int i = 0; i < M; ++i) {
		printf("%d ", d[i]);
	}
	printf("\n");
}

示例结果:

$ gcc ex004.c -o demo
$ ./demo
各年龄阶段人数数量:
10 9 8 4 10 8 7 7 6 11 20

案例ex05: 删除一维数组中所有相同的数,使之只剩一个。

1 题目

函数:fun()

功能:删除一维数组中所有相同的数,使之只剩一个。

描述:数组中的数据已经按照从小到大排列,函数返回删除后数组中元素的个数

举例:

一维数组中的数据是:2,2,2,2,3,3,4,4,5,6,6,6,7,7,8,9,9,9,10,10

删除后数组中的内容是:2,3,4,5,6,7,8,9,10

2 思路

初始化没有重复元素最右方的指针 a

当前元素与前一个元素进行比较,如果相同,则调到下一个,否则指针a+1

3 代码

#include <stdio.h>
#include <stdlib.h>
#define N 20

/**
函数:fun()
功能:删除一维数组中所有相同的数,使之只剩一个。
描述:数组中的数据已经按照从小到大排列,函数返回删除后数组中元素的个数
举例:
一维数组中的数据是:2,2,2,2,3,3,4,4,5,6,6,6,7,7,8,9,9,9,10,10
删除后数组中的内容是:2,3,4,5,6,7,8,9,10
**/



int fun(int *s) {
	int a = 1;
	for (int i = 1; i < N; ++i) {
		if (*(s+i) != *(s+i-1)) {  // 当前元素与前一个元素进行比较,如果相同,则调到下一个,否则a+1
			*(s+a) = *(s+i);
			a++;
		}
	}
	return a;
}


int main(int argc,char const *argv[]) {
	int s[N] = {2,2,2,2,3,3,4,4,5,6,6,6,7,7,8,9,9,9,10,10};
	int cnt = fun(s);
	printf("删除后的数组元素个数为:%d\n",cnt);
}

示例结果:

$ gcc ex005.c -o demo
$ ./demo
删除后的数组元素个数为:9

案例ex06: 移动字符串中内容

1 题目

函数:fun()

功能:移动字符串中内容

描述:移动规则如下:把第1到第m个字符,平移到字符串的最后,把m+1到最后的字符移到字符串的前部

举例:字符串原有内容为ABCDEFGHIJK,m的值为3,则移动后,字符串中的内容应该是DEFGHIJKABC

2 思路

为了不产生额外的空间复杂度,本次解决将一位一位移动

将数组第一个位置的元素保存到一个临时变量temp中,从第二位开始集体向左移动,最后将temp元素保存到最后一位

循环 m 次,从而达到最后的效果

3 代码

#include <stdio.h>
#include <string.h>
#define N 10

/**
函数:fun()
功能:移动字符串中内容
描述:移动规则如下:把第1到第m个字符,平移到字符串的最后,把m+1到最后的字符移到字符串的前部。
举例:字符串原有内容为ABCDEFGHIJK,m的值为3,则移动后,字符串中的内容应该是DEFGHIJKABC
**/


void fun(char *s, int m) {
	int temp;
	for (int i = 0; i < m; ++i) {
		temp = s[0];
		for (int j = 1; j < N; ++j) {
			s[j-1] = s[j];
		}
		s[N-1] = temp;
	}
}


int main(int argc, char const *argv[]) {
	char s[N] = "ABCDEFGHIJ";
	int m = 3;
	printf("移动前的字符串:%s\n", s);
	fun(s, m);
	printf("移动后的字符串:%s\n", s);
}

示例结果:

$ gcc ex006.c -o demo
$ ./demo
移动前的字符串:ABCDEFGHIJ
移动后的字符串:DEFGHIJABC

案例ex07: 求数字的低n-1位的数

1 题目

函数:unsigned fun(unsigned w)

功能:求数字的低n-1位的数

描述:w 是一个大于10的无符号整数,若 w 是 n(n>=2)位的整数,函数求出来w的低n-1位的数作为函数值返回

举例:w 值为5923,则函数返回 923

2 思路

两步走:

  1. 先判断当前无符号整数的位数,记录位数*10。例如:如果有三位,那么记录time=100
  2. 根据time计算后 n-1 位,即:w-((w/time)*time)

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
函数:unsigned fun(unsigned w)
功能:求数字的低n-1位的数
描述:w 是一个大于10的无符号整数,若 w 是 n(n>=2)位的整数,函数求出来w的低n-1位的数作为函数值返回
举例:w 值为5923,则函数返回 923
**/

unsigned fun(unsigned w) {
	// 先判断数字的位数
	int temp_w = w;
	int time = 1;   // 位数10的指数次
	while(temp_w > 0) {
		time*=10;
		temp_w = temp_w/10;
	}
	time = time/10;
	// 计算返回
	return w-((w/time)*time);
}


int main(int argc, char const *argv[]) {
	int w;
	printf("请一个大于10的无符号整数:");
	scanf("%d", &w);
	printf("无符号整数低n-1位的数为:%d\n", fun(w));
}

示例结果:

$ gcc ex007.c -o demo
$ ./demo
请一个大于10的无符号整数:12345
无符号整数低n-1位的数为:2345
$ ./demo
请一个大于10的无符号整数:765432
无符号整数低n-1位的数为:65432

案例ex08: 使数组的左下三角元素中的值乘以n

1 题目

函数:fun(int a[][N], int n)

功能:使数组的左下三角元素中的值乘以n

描述:程序定义了 N*N 的二维数组,并在主函数中自动赋值。

举例:

若 n 的值为3,a数组中的值为

1 9 7

3 9 7

2 3 8

则返回主程序后 a 数组中的值应该为

3 9 7

9 27 7

6 9 24

2 思路

利用二重循环解决

在第二层需要进行一点注意的地方

3 代码

# include <stdio.h>
# include <stdlib.h>
# define N 3

/**
函数:fun(int a[][N], int n)
功能:使数组的左下三角元素中的值乘以n
描述:程序定义了 N*N 的二维数组,并在主函数中自动赋值。
举例:
若 n 的值为3,a数组中的值为
1  9  7
3  9  7
2  3  8
则返回主程序后 a 数组中的值应该为
3  9  7
9  27 7
6  9  24
*/

void fun(int a[][N], int n) {
	for (int i = 0; i < N; ++i) {
		for (int j = 0; j <= i; ++j) {
			a[i][j] = a[i][j] * n;
		}
	}
}

int main(int argc, char const *argv[]) {
	int a[N][N] = {{1,9,7}, {3,9,7}, {2,3,8}};
	int n = 3;

	printf("原数组为:\n");
	for (int i = 0; i < N; ++i) {
		for (int j = 0; j < N; ++j) {
			printf("%d\t", a[i][j]);
		}
		printf("\n");
	}

	fun(a, n);
	printf("计算后数组:\n");
	for (int i = 0; i < N; ++i) {
		for (int j = 0; j < N; ++j) {
			printf("%d\t", a[i][j]);
		}
		printf("\n");
	}
}

示例结果:

$ gcc ex008.c -o demo
$ ./demo
原数组为:
1	9	7
3	9	7
2	3	8
计算后数组:
3	9	7
9	27	7
6	9	24

案例ex09: 移动一维数组的内容

1 题目

函数:fun()

功能:移动一维数组的内容

描述:若数组中有n个整数,要求把下标从0到p(含p,p<=n-1)的元素平移到数组的最后

举例:

一维数组:1,2,3,4,5,6,7,8,9,10,p的值为3

移动后:5,6,7,8,9,10,1,2,3,4

2 思路

循环 p 次:

将下标为0的数字进行临时存放,然后将后面的数字平移到前面,最后将临时存放的数字放到最后一位

3 代码

#include <stdio.h>
#include <stdlib.h>
#define N 10

/**
函数:fun()
功能:移动一维数组的内容
描述:若数组中有n个整数,要求把下标从0到p(含p,p<=n-1)的元素平移到数组的最后
举例:
一维数组:1,2,3,4,5,6,7,8,9,10,p的值为3
移动后:5,6,7,8,9,10,1,2,3,4
**/


void fun(int *a, int p) {
	int temp;
	for (int i = 0; i <= p; ++i) {
		temp = a[0];	// 临时存放第一个数字
		for (int j = 1; j < N; ++j) { 	// 循环将后续数字进行平移
			a[j-1] = a[j];
		}
		a[N-1] = temp;	// 临时数字存放到最后一位
	}
}


int main(int argc, char const *argv[]) {
	int a[10] = {1,2,3,4,5,6,7,8,9,10};
	int p = 3;

	printf("原始数组内容:\n");
	for (int i = 0; i < N; ++i) {
		printf("%d ", a[i]);
	}
	printf("\n");

	fun(a, p);
	printf("平移后数组内容:\n");
	for (int i = 0; i < N; ++i) {
		printf("%d ", a[i]);
	}
	printf("\n");
}

示例结果:

$ gcc ex009.c -o demo
$ ./demo
原始数组内容:
1 2 3 4 5 6 7 8 9 10
平移后数组内容:
5 6 7 8 9 10 1 2 3 4

案例ex10: 删除字符串中所有的空格

1 题目

函数:fun()

功能:删除字符串中所有的空格

举例:

主函数中输入“fds afadsf adf d dsf 67d”

则输出:“fdsafadsfadfddsf67d”

2 思路

设置两指针,begin 和 end

begin 和 end 同时从头开始向后移动:

  • 当遇到空格的时候,end 向后移动,begin不变
  • 当 end 位置不为空格的时候,将 end 位置的字符填充到 begin 的位置

最后,在完成去除空格的操作后,在最后添加 '\0'

3 代码

#include <stdio.h>
#include <stdlib.h>
#define N 30

/**
函数:fun()
功能:删除字符串中所有的空格
举例:
主函数中输入“fds afadsf adf d  dsf   67d”
则输出:“fdsafadsfadfddsf67d”
**/

void fun(char *s) {
	int begin = 0, end = 0;
	while(s[end]!='\0') {
		if (s[end] != ' ') {
			s[begin] = s[end];
			begin++;
			end++;
		} else {
			end++;
		}
	}
	s[begin] = '\0';  	// 去除空格后,在最后一位加 '\0'
}

int main(int argc, char const *argv[]) {
	char s[N] = "fds afadsf adf d  dsf   67d";
	printf("原始字符串:%s\n", s);
	fun(s);
	printf("去空格后字符串:%s\n", s);
}

示例结果:

$ gcc ex010.c -o demo
$ ./demo
原始字符串:fds afadsf adf d  dsf   67d
去空格后字符串:fdsafadsfadfddsf67d

案例ex11: 使用指针实现整数逆序排序

1 题目

函数:fun()

功能:使用指针实现整数逆序排序

描述:在main函数中实现,输入三个数字,使用指针实现三个数字的逆序排序

2 思路

使用指针实现数字的操作

3 代码

#include <stdio.h>
#include <stdlib.h>


/**
函数:fun()
功能:使用指针实现整数逆序排序
描述:在main函数中实现,输入三个数字,使用指针实现三个数字的逆序排序
**/

void swap(int *p1, int *p2) {
	int temp;
	temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

void fun(int *p1, int *p2, int *p3) {
	if (*p1 < *p2)
		swap(p1, p2);
	if (*p1 < *p3)
		swap(p1, p3);
	if (*p2 < *p3)
		swap(p2, p3);
}


int main(int argc, char const *argv[]) {
	int a, b, c;
	int *q1, *q2, *q3;
	printf("请输入三个数字:");
	scanf("%d, %d, %d", &a, &b, &c);
	q1 = &a;
	q2 = &b;
	q3 = &c;

	printf("逆序前的数字:%d, %d, %d\n", *q1, *q2, *q3);
	fun(q1, q2, q3);
	printf("逆序后的数字:%d, %d, %d\n", *q1, *q2, *q3);
}

示例结果:

$ gcc ex011.c -o demo
$ ./demo
请输入三个数字:3,9,1
逆序前的数字:3, 9, 1
逆序后的数字:9, 3, 1

$ ./demo
请输入三个数字:1,2,3
逆序前的数字:1, 2, 3
逆序后的数字:3, 2, 1

案例ex12: 指向结构体变量的指针

1 题目

功能:指向结构体变量的指针

描述:通过结构体指针变量实现显示学生信息

2 思路

熟悉结构体的使用

熟悉指针和结构体的混合使用

3 代码

#include <stdio.h>

/**
功能:指向结构体变量的指针
描述:通过结构体指针变量实现显示学生信息
**/

struct student {
	int num;
	char name[20];
	char sex;
	int age;
	float score;
};

int main(int argc, char const *argv[]) {
	struct student stu = {
		1001, "计算广告生态", 'M', 28, 98.5
	};
	struct student *s = &stu;

	printf("No.\t%d\n", s->num);
	printf("Name.\t%s\n", s->name);
	printf("Sex.\t%c\n", s->sex);
	printf("Age.\t%d\n", s->age);
	printf("Score.\t%d\n", s->score);
}

示例结果:

$ gcc ex012.c -o demo
$ ./demo
No.	1001
Name.	计算广告生态
Sex.	M
Age.	28
Score.	73896

案例ex13: 使用指针输出数组元素

1 题目

目标:熟悉指针和数组的底层逻辑

功能:使用指针输出数组元素

描述:通过指针将数组中你那个各个元素值进行打印输出

2 要点

a. 指向数组的指针实现输出数组元素,定义一个指向数组的指针用来灵活操作数组

int a[10];
int *p;
// 指针 p 指向数组的方法,下面两种都是可以的.
// a 本身就是数组的其实地址,&a[0] 也是数组的起始地址
p = a; 				
p = &a[0];

b. 指针既是指向变量地址的又是决定指向变量地址的位数的。例如

int p = &a[0];

既是指向数组a的首地址 又是说明了每次指向都int类型的数据,即 4 个字节。

所以,在指定 p 的 基类型 后,通过指针指向数组,每次 p++ 都是会跳动4个字节,到达下一个位置a[1]。

即:*(p+1) 即取得 a[1] 的数据

3 代码

#include <stdio.h>
#define N 10

/**
目标:熟悉指针和数组的底层逻辑
功能:使用指针输出数组元素
描述:通过指针将数组中你那个各个元素值进行打印输出
**/


int main(int argc, char const *argv[]) {
	int a[N];
	int *p;
	printf("请输入 10 个数字: \n");
	for (int i = 0; i < N; ++i) {
		scanf("%d", &a[i]);
	}
	printf("\n");

	for (p = a; p < a+10; ++p)	{	// 指针指向进行数组内容打印
		printf("数组中的内容:\n%d\n", *p);
	}
	return 0;
}

示例结果:

$ gcc ex013.c -o demo
$ ./demo
请输入 10 个数字:
1
2
3
4
5
6
7
8
9
10

数组中的内容:
1
2
3
4
5
6
7
8
9
10

案例ex14: 找出数列中的最大值和最小值

1 题目

函数:max_min()

功能:找出数列中的最大值和最小值

描述:使用指针查找数列中的最大值和最小值(使用指针从而避免使用返回值)

2 思路

使用指针从而避免使用返回值

在主函数中定义最大值 max 和最小值 min,将 max 和 min 的地址传递给函数处理,计算结果直接放到主函数中定义的 max 和 min 的地址中

这样做代码执行高效并且思路清晰

3 代码

#include <stdio.h>
#include <stdlib.h>
#define N 10

/**
函数:max_min()
功能:找出数列中的最大值和最小值
描述:使用指针查找数列中的最大值和最小值
**/

void max_min(int *a, int *max, int *min) {
	*max = *a;		// 将数组下标为 0 的数字初始化给max
	*min = *a;		// 将数组下标为 0 的数字初始化给min
	for (int i = 0; i < N; ++i) {
		if(*max < *(a+i))		// 将大于max的值赋值给max
			*max = *(a+i);
	}

	for (int i = 0; i < N; ++i) {
		if(*min > *(a+i))		// 将小于min的值赋值给min
			*min = *(a+i);
	}
}

int main(int argc, char const *argv[]) {
	int i, a[N];
	int max, min;
	printf("请输入 10 个数字:\n");
	for (int i = 0; i < N; ++i)	{
		scanf("%d", a+i);
		// scanf("%d", &a[i]);
	}
	max_min(a, &max, &min);
	printf("输入的 10 个数字为: ");
	for (int i = 0; i < N; ++i)	{
		printf("%d ", *(a+i));
	}	
	printf("\n最大值为:%d\n", max);
	printf("\n最小值为:%d\n", min);
}

示例结果:

$ gcc ex014.c -o demo
$ ./demo
请输入 10 个数字:
3
2
1
10
9
8
7
6
5
4
输入的 10 个数字为: 3 2 1 10 9 8 7 6 5 4
最大值为:10

最小值为:1

案例ex15: 使用指针的指针输出字符串

1 题目

功能:使用指针的指针输出字符串

描述:

使用指针的指针输出字符串。

首先要使用指针数组创建一个字符串数组,然后定义指向指针的指针,使其指向字符串数组,并使用其输出数组的字符串

2 思路

char **p;

这里指向指针的指针,表示指针变量 p 是指向一个指针变量。*p 就表示 p 指向另外一个指针变量,即一个地址。

**p 表示 p 指向指针变量指向的对象的值。 例如:* (p+1) 表示 accounts[1] 的内容,指向 accounts[1] 的首地址

3 代码

#include <stdio.h>
#define N 5

/**
函数:fun()
功能:使用指针的指针输出字符串
描述:使用指针的指针输出字符串。
首先要使用指针数组创建一个字符串数组,然后定义指向指针的指针,使其指向字符串数组,并使用其输出数组的字符串
**/

int main(int argc, char const *argv[]) {
	char *accounts[] = {
		"你好 C语言 1",
		"你好 C语言 2",
		"你好 C语言 3",
		"你好 C语言 4",
		"你好 C语言 5"
	};																		// 指针数组创建字符串数组

	char **p;
	p = accounts;													// 注意这里一定是二重指针来指向accounts
	for (int i = 0; i < N; ++i) {
		printf("%s\n", *(p+i));							// 使用指针将字符串数组中的字符串打印出来
	}
}

示例结果:

$ gcc ex015.c -o demo
yaojianguodeMacBook-Pro:ex100 yaojianguo$ ./demo
欢迎关注公众号, 计算广告生态1
欢迎关注公众号, 计算广告生态2
欢迎关注公众号, 计算广告生态3
欢迎关注公众号, 计算广告生态4
欢迎关注公众号, 计算广告生态5

案例ex16: 使用指向指针的指针对字符串排序

1 题目

函数:sort()

功能:使用指向指针的指针对字符串排序

描述:

使用指向指针的指针对字符串排序,输出是按照字母顺序进行排序

2 思路

a. 熟悉指向指针的指针的使用

char *nums[]={"", ""};
char **p;
p = nums;

熟悉 *p 指向的是 nums[0] 的首地址

b. 引用模块#include <string.h>

使用函数 int strcmp(const char *str1, const char *str2) 进行字符串的比较

该函数返回值如下:

  • 如果返回值小于 0,则表示 str1 小于 str2。
  • 如果返回值大于 0,则表示 str1 大于 str2。
  • 如果返回值等于 0,则表示 str1 等于 str2。

3 代码

#include <stdio.h>
#include <string.h>
#define N 10

/**
函数:fun()
功能:使用指向指针的指针对字符串排序
描述:
使用指向指针的指针对字符串排序,输出是按照字母顺序进行排序
**/

void sort(char **p) {
	char *temp; 	// 排序交换时的临时变量
	for (int i = 0; i < N; ++i) {
		for (int j = i+1; j < N; ++j) {
			if (strcmp(*(p+i), *(p+j)) > 0) { 	// 使用strcmp进行字符串的比较
				temp = *(p+i);
				*(p+i) = *(p+j);
				*(p+j) = temp;
			}
		}
	}
}

int main(int argc, char const *argv[]) {
	int n = 5;
	char **p;
	char *nums[] = {
		"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"
	};
	p = nums;			// 使用指向指针的指针指向字符串数组
	printf("排序前的数组内容为:\n");
	for (int i = 0; i < N; ++i) {
		printf("%s ", *(p+i));
	}
	printf("\n");

	sort(p);
	printf("排序后的数组内容为:\n");
	for (int i = 0; i < N; ++i) {
		printf("%s ", *(p+i));
	}
	printf("\n");
}

示例结果:

$ gcc ex016.c -o demo
$ ./demo
排序前的数组内容为:
one two three four five six seven eight nine ten
排序后的数组内容为:
eight five four nine one seven six ten three two

案例ex17: 使用指针连接两个字符串

1 题目

函数:connect()

功能:使用指针连接两个字符串

描述:

实现两个已知字符串的连接,放到另外一个字符串数组中,然后将连接好的字符串进行打印显示

2 思路

使用 字符型指针变量指向字符串的指针 做函数的参数来实现字符串的连接

注意初始化 char *conn来存储新字符串的时候,必须先分配存储空间

char *conn = (char *)malloc(strlen(str1)+strlen(str2)+1);
memset(conn, strlen(str1)+strlen(str2)+1, 0); // 可选,初始化填充内存空间

另外,使用指针的特性,使得 conn 不断被赋值, str1 和 str2 指向的存储地址,达到字符串的连接

3 代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/**
函数:connect()
功能:使用指针连接两个字符串
描述:
实现两个已知字符串的连接,放到另外一个字符串数组中,然后将连接好的字符串进行打印显示
**/


void connect(char *str1, char *str2, char *conn) {
	while(*str1 != '\0') {
		*conn = *str1;
		conn++;
		str1++;
	}
	*conn = ','; 	// 两个字符串直接添加逗号
	conn++;
	while(*str2 != '\0') {
		*conn = *str2;
		conn++;
		str2++;
	}
	*conn = '\0';
}


int main(int argc, char const *argv[]) {
	char *str1 = "技术分享园子";
	char *str2 = "计算广告生态";
	char *conn = (char *)malloc(strlen(str1)+strlen(str2)+1);  	// 为 conn 分配空间,否则是无效地址
	memset(conn, strlen(str1)+strlen(str2)+1, 0);               // 初始化 conn 指向的存储地址
	printf("第一个字符串:%s\n", str1);
	printf("第二个字符串:%s\n", str2);
	connect(str1, str2, conn);
	printf("连接后的字符串是:\n");
	printf("%s\n", conn);
}

示例结果:

$ gcc ex017.c -o demo
$ ./demo
第一个字符串:技术分享园子
第二个字符串:计算广告生态
连接后的字符串是:
技术分享园子,计算广告生态

案例ex18: 用指针实现逆序存放数组元素的值

1 题目

函数:invert()

功能:用指针实现逆序存放数组元素的值

描述:使用指针将数组中的元素值逆序放置,并且将结果打印出来

2 思路

1. 程序变量中两个数字进行交换的三种方法

第一种:引入一个中间量

temp=a;
a=b;
b=temp;

第二种:要注意的是a+b可能会超过int的范围,这个是该方法的缺点

a=a+b;
b=a-b;
a=a-b;

第三种:这种方法效率最高,推荐使用

a=a^b;
b=a^b;
a=a^b;

2. 指针的使用

使用指针对数组内容进行操作,数组 a, a 指向数组首位置,a+N-1 指向最后一位。从而利用循环对数组的首尾数字逐个替换

3 代码

#include <stdio.h>
#include <stdlib.h>
#define N 10

/**
函数:invert()
功能:用指针实现逆序存放数组元素的值
描述:使用指针将数组中的元素值逆序放置,并且将结果打印出来
**/


void invert(int *a) {
	int *begin, *end, mid;   // 定义首尾指针
	begin = a;
	end = a+N-1;
	mid = (N-1)/2;		     // 中间位置
	int temp;
	for (int i = 0; i <= mid; ++i){	// 循环,首尾指针内容变换
		// temp = *begin;
		// *begin = *end;
		// *end = temp;
		*begin = *begin ^ *end;
		*end = *begin ^ *end;
		*begin = *begin ^ *end;

		++begin;
		--end;
	}
}


int main(int argc, char const *argv[]) {
	int a[N] = {1,2,3,4,5,6,7,8,9,10};
	printf("当前数组中的值为:");
	for (int i = 0; i < N; ++i) {
		printf("%d ", *(a+i));
	}
	printf("\n");
	
	invert(a);
	printf("使用指针逆序后数组中的值为:");
	for (int i = 0; i < N; ++i) {
		printf("%d ", *(a+i));
	}
	printf("\n");
}

示例结果:

$ gcc ex018.c -o demo
$ ./demo
当前数组中的值为:1 2 3 4 5 6 7 8 9 10
使用指针逆序后数组中的值为:10 9 8 7 6 5 4 3 2 1

案例ex19: 用指针数组构造字符串数组

1 题目

功能:用 指针数组 构造字符串数组

描述:实现输入一个星期中对应的第几天,可以显示其英文名

目标:熟悉对字符串数组对应的指针的使用

2 思路

  • 要点:通过构造一个字符串数组来指定数组中元素的元素值。

  • 指针数组:即数组中都是指针类型的数据,指针数组中的每个元素都是一个指针

    定义:

类型名 *数组名[数组长度];
例如:
char *[5];
其中 p 是一个指针数组,该数组是由 5 个数据元素组成,每个元素相当于一个指针变量,都可以指向一个字符串变量
  • 注意点:*p[4] 与 (*p)[4] 的区别要知道, (*p)[4] 中的 p 是一个指向一维数组的指针变量

3 代码

#include <stdio.h>

/**
功能:用指针数组构造字符串数组
描述:实现输入一个星期中对应的第几天,可以显示其英文名
目标:熟悉对字符串数组对应的指针的使用
**/


int main(int argc, char const *argv[]) {
	char *week[] = {
		"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"
	};
	int w;	// 用来记录周几
	printf("请输入星期几(输入阿拉伯数字):");
	scanf("%d", &w);
	printf("Today is %s.\n", *(week+w-1));
	return 0;
}

示例结果:

$ gcc ex019.c -o demo
$ ./demo
请输入星期几(输入阿拉伯数字):5
Today is Friday

案例ex20: 用指针函数输出学生成绩

1 题目

函数:search()

功能:用指针函数输出学生成绩

描述:指针函数的使用,输入学生的序号,将在窗口输出该序号对应的学生的成绩

2 思路

  • 指向函数的指针变量的形式如下:

    数据类型 (*指针变量名)()

    例如;

    int * search()
  • *(p)() 表示定义一个指向函数的指针变量,用来存放函数入口地址。在程序设计过程中,将一个函数地址赋值给它,它就指向那个函数。函数指针变量赋值写法:

    p = min;

    在赋值的时候,只给出函数名即可 ,函数名即地址

    在使用函数指针调用函数的时候,要写出函数的参数,例如:

    m = (*p)(a,b);

3 代码

#include <stdio.h>

/**
函数:search()
功能:用指针函数输出学生成绩
描述:指针函数的使用,输入学生的序号,将在窗口输出该序号对应的学生的成绩
**/

int * search(int (*p)[4], int n) {
	int *pt = *(p+n);
	return pt;
}


int main(int argc, char const *argv[]) {
	int score[][4] = {
		{98,56,89,87},
		{88,56,87,80},
		{92,56,84,82},
		{78,56,90,67}
	};					// 声明数组,对应四个学生的各科成绩
	int no;				// 保存学生编号
	printf("输入学生编号:");
	scanf("%d", &no);
	int *p = search(score, no);
	printf("学生编号为 %d 的学生成绩为:\n", no);
	for (int i = 0; i < 4; ++i) {
		printf("%d\t", *(p+i));
	}
	putchar('\n');
}

示例结果:

$ gcc ex020.c -o demo
$ ./demo
输入学生编号:3
学生编号为3的学生成绩为:
78	56	90	67

案例ex21: 寻找相同元素的指针

1 题目

函数:find()

功能:寻找相同元素的指针

描述:比较两个有序数组的元素,输出两个数组中第一个相同的值

2 思路

利用有序数组有序的特点

建立一个指针函数,这个函数返回值为指针类型,即一个地址。定义如下:

int * find(int *a, int *b, int m, int n);

在程序中调用该函数:

int * p = find(a, b, sizeof(a)/sizeof(a[0]), sizeof(b)/sizeof(b[0]));

返回一个指向整型变量的指针

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
函数:find()
功能:寻找相同元素的指针
描述:比较两个有序数组的元素,输出两个数组中第一个相同的值
**/

int * find(int *a, int *b, int m, int n) {
	int *pa = a;
	int *pb = b;
	while(pa < a+m && pb < b+n) {
		if (*pa < *pb) {
			pa++;
		}
		else if(*pa > *pb) {
			pb++;
		}
		else {
			return pa;
		}
	}
}


int main(int argc, char const *argv[]) {
	int *p;
	int a[] = {2,5,7,9,11,34,56};		// 声明两个数组
	int b[] = {3,6,8,10,12,34,59};	// 声明两个数组	
	printf("两个数组的内容分别是:\n");
	for (int i = 0; i < sizeof(a)/sizeof(a[0]); ++i) { 	// 打印数组
		printf("%d ", *(a+i));
	}
	printf("\n");
	for (int j = 0; j < sizeof(b)/sizeof(b[0]); ++j) { 	// 打印数组
		printf("%d ", *(b+j));
	}
	p = find(a, b, sizeof(a)/sizeof(a[0]), sizeof(b)/sizeof(b[0]));
	printf("\n相同的数字是:%d\n", *p);
}

示例结果:

$ gcc ex021.c -o demo
$ ./demo
两个数组的内容分别是:
2 5 7 9 11 34 56
3 6 8 10 12 34 59
相同的数字是:34

案例ex22: 查找成绩不及格的学生

1 题目

函数:search()

功能:查找成绩不及格的学生

描述:有 4 名学生的成绩,找出至少有一科不及格的学生,并将成绩列表输出(规定60以下为不及格)

目标:重点理解 int (*p)[N] 的使用方法

2 思路

int*p[5] 和 int(*p)[5] 的区别

  • int *p[5],首先它是一个数组,它的大小是5,p里面存放的数据都是类型是int *,也就是整型指针。 所以它叫指针数组。

  • int (*p)[5],首先p是一个指针, 指向大小为5的数组 ,因此这叫数组指针。通常用在二维数组的操作上

    • 注意指针的类型,从 int(*p)[5] 可以看到, p 的类型不是int *, 而是 int(*)[5],p 被定义为指向一维数组的指针变量,里面有 5 个元素,因此,p 的基类型是一维数组,长度为 20 字节。
    • *(p+2)+3, 括号中的 2 是指的以 p 的基类型(一维整型数组)的长度为单位的,即,p 每增加 1,地址就增加 20 个字节(5 个元素,每个元素4个字节)。而 *(p+2)+3 括号外的数字 3,不是以 p 的基类型的长度为单位的,而是一维数组的下一个位置
  • 先看一个简单的引例

    #include <stdio.h>
    
    void search(float (*p)[4], int n) {
    	printf("序号为 %d 的学生的成绩:\n", n);
    	for (int i = 0; i < 4; ++i) {
    		printf("%5.2f ", *(*(p+n)+i));
    	}
    }
    
    int main(int argc, char const *argv[]) {
    	float score[3][4]={{60,75,82,91},{75,81,91,90},{51,65,78,84}};
    	search(score, 2);  // 计算得出序号为 2 的学生的成绩
    	getchar();
    }
    
    ~~~~~~~~~~~操作结果~~~~~~~~~~~~
    $ gcc ex022-1.c -o demo
    ./yaojianguodeMacBook-Pro:C语言100题集合代码 yaojianguo$ ./demo
    序号为 2 的学生的成绩:
    51.00 65.00 78.00 84.00

3 代码

#include <stdio.h>
#include <stdlib.h>
#define N 5

/**
函数:search()
功能:查找成绩不及格的学生
描述:有 4 名学生的成绩,找出至少有一科不及格的学生,并将成绩列表输出(规定60以下为不及格)
**/

int * search(int (*p)[N]) {
	int * pt = *(p+1);
	for (int i = 0; i < 4; ++i) {
		if (*(*(p+0)+i) < 60) {   // 这块可以写成*(*p+i) < 60,也就是平常看到了*(*(p+m)+n))来取到二维数组中列向的值
			pt = *p;
		}
	}
	return pt;
}

int main(int argc, char const *argv[]) {
	int * p;
	int score[][N]={{60,75,82,91},{75,81,91,90},{51,65,78,84},{65,72,78,72},{75,70,98,92}}; 	// 四个学生的成绩
	for (int i = 0; i < N; ++i) {
		p = search(score+i);
		if (p==*(score+i)) {
			printf("成绩不及格学生的成绩分别是:\n");
			for (int j = 0; j < 4; ++j) {
				printf("%d ", *(p+j));
			}			
		}
	}
	getchar();
}

示例结果:

$ gcc ex022.c -o demo
$ ./demo
成绩不及格学生的成绩分别是:
51 65 78 84

案例ex23: 使用指针实现冒泡排序

1 题目

函数:bubble_order()

功能:使用指针实现冒泡排序

描述:实现C语言经典的冒泡排序

2 思路

冒泡排序的基本思路:

如果对 n 个数进行冒泡排序,则需要进行 n-1 躺比较,在第 1 趟比较中要进行 n-1 次两两比较,在第 j 趟比较中要进行 n-j 次两两比较

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
函数:bubble_order()
功能:使用指针实现冒泡排序
描述:实现C语言经典的冒泡排序
**/

void bubble_order(int *a, int n) {
	for (int i = 0; i < n-1; ++i) {
		for (int j = 0; j < n-1-i; ++j) {
			if (*(a+j) > *(a+j+1)) {
				*(a+j) = *(a+j) ^ *(a+j+1);
				*(a+j+1) = *(a+j) ^ *(a+j+1);
				*(a+j) = *(a+j) ^ *(a+j+1);
			}
		}
	}
}

int main(int argc, char const *argv[]) {
	int a[20], n;
	printf("请输入要排序元素的个数:\n");
	scanf("%d", &n);
	printf("请输入各个元素\n");
	for (int i = 0; i < n; ++i) {
		scanf("%d", a+i);
	}
	printf("排序前元素的内容为:\n");
	for (int j = 0; j < n; ++j) {
		printf("%d ", *(a+j));
	}
	bubble_order(a, n);
	printf("\n排序后元素的内容为:\n");
	for (int j = 0; j < n; ++j) {
		printf("%d ", *(a+j));
	}
	printf("\n");
	getchar();
}

示例结果:

$ gcc ex023.c -o demo
$ ./demo
请输入要排序元素的个数:
5
请输入各个元素
3
7
1
9
4
排序前元素的内容为:
3 7 1 9 4
排序后元素的内容为:
1 3 4 7 9

案例ex24: 输入月份号并输出英文月份名

1 题目

功能:输入月份号并输出英文月份名

描述:

使用指针数组创建一个含有月份英文名的字符串数组

并使用指向指针的指针指向这个字符串数组,实现输出数组中的指定字符串

2 思路

使用指针的指针实现对字符串数组中的字符串的输出

*month[] 属于指针数组,*month本身就是指针,数组中都是存放着指针。那么 month是指针数组的首地址,**p=month 指向数组中的每个元素

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:输入月份号并输出英文月份名
描述:
使用指针数组创建一个含有月份英文名的字符串数组
并使用指向指针的指针指向这个字符串数组,实现输出数组中的指定字符串
**/

int main(int argc, char const *argv[]) {
	char *month[] = {
		"January",
		"February",
		"March",
		"April",
		"May",
		"June",
		"July",
		"August",
		"September",
		"October",
		"November",
		"December"
	};
	int m;
	char **p;   		// 指向指针的指针变量
	p = month;		// 数组首地址赋值给指针变量
	printf("输入一个月份号(阿拉伯数组):");
	scanf("%d", &m);
	printf("本月是:");
	printf("%s\n", *(p+m-1));
	getchar();
	return 0;
}

示例结果:

$ gcc ex024.c -o demo
$ ./demo
输入一个月份号(阿拉伯数组):3
本月是:March
$ ./demo
输入一个月份号(阿拉伯数组):9
本月是:September

案例ex25: 使用指针插入元素

1 题目

函数:insert()

功能:使用指针插入元素

描述:在有序(升序)的数组中插入一个数,使得插入的数组仍然有序

2 思路

  1. 参数传递,使用指针变量
  2. 插入数字,找到插入点, 从数组的末端逐个向后移动 ,最后将要插入的数字放到插入点

3 代码

#include <stdio.h>
#include <stdlib.h>
#define N 10

/**
函数:insert()
功能:使用指针插入元素
描述:在有序(升序)的数组中插入一个数,使得插入的数组仍然有序
**/

void insert(int *a, int num) {
	printf("%d\n", num);
	int i=0, j=N+1;
	for ( ; i <= N; ++i) {
		if (num < *(a+i))
			break;			// 插入的数据记录大于数组中数据的位置
	}
	for ( ; j > i; --j) {
		*(a+j) = *(a+j-1);
	}
	*(a+i) = num;
}


int main(int argc, char const *argv[]) {
	int a[N+1], add_num;
	int *p;
	printf("输入十个数字:\n");
	for (int i = 0; i < N; i++)
		scanf("%d", &a[i]);
	printf("输入要插入的数字: ");
	scanf("%d", &add_num);

	insert(a, add_num);
	printf("插入后的数组为: ");
	for (int i = 0; i < N+1; ++i) {
		printf("%d ", a[i]);
	}
	printf("\n");
	return 0;
}

示例结果:

$ gcc ex025.c -o demo
$ ./demo
输入十个数字:
1
2
3
4
5
7
8
9
10
11
输入要插入的数字: 6
6
插入后的数组为: 1 2 3 4 5 6 7 8 9 10 11

案例ex26: 使用指针交换两个数组中的最大值

1 题目

函数:max()、swap()

功能:使用指针交换两个数组中的最大值

描述:

输入两个五个元素的数组,使用指针将两个数组中的最大值进行交换

并输出最大值交换之后的两个数组

2 思路

以下都使用指针来进行实现

  1. 找到最大值
    创建 int *max(int *a) 函数,指针指向找出的最大值
  2. 交换最大值
    创建 void swap(int *p1, int *p2) 找到的两个最大值进行交换,即进行指针指向的内容进行交换

3 代码

#include <stdio.h>
#include <stdlib.h>
#define N 5

/**
函数:max()、swap()
功能:使用指针交换两个数组中的最大值
描述:
输入两个五个元素的数组,使用指针将两个数组中的最大值进行交换
并输出最大值交换之后的两个数组
**/


int * max(int *a) {
	int * p = a;
	for (int i = 1; i < N; ++i) {
		if (*(a+i) > *p)
			p = a+i;
	}
	return p;
}

void swap(int *p1, int *p2) {
	*p1 = *p1 ^ *p2;
	*p2 = *p1 ^ *p2;
	*p1 = *p1 ^ *p2;
}


int main(int argc, char const *argv[]) {
	int a[N], b[N];
	int * max_a, * max_b;
	// 1. 初始化两个数组
	printf("输入数组 a 的5个数字:\n");
	for (int i = 0; i < N; ++i) {
		scanf("%d", (a+i));
	}
	printf("输入数组 b 的5个数字:\n");
	for (int j = 0; j < N; ++j) {
		scanf("%d", (b+j));
	}
	printf("\n数组 a 的5个数字为: ");
	for (int i = 0; i < N; ++i) {
		printf("%d ", *(a+i));
	}
	printf("\n数组 b 的5个数字为: ");
	for (int j = 0; j < N; ++j) {
		printf("%d ", *(b+j));
	}

	// 2. 找出各数组中的最大值
	max_a = max(a);
	max_b = max(b);
	printf("\n数组 a 的最大值:%d", *max_a);
	printf("\n数组 b 的最大值:%d", *max_b);

	// 3. 对两个最大值进行交换
	swap(max_a, max_b);
	printf("\n交换最大值之后的数组 a 的5个数字为:");
	for (int i = 0; i < N; ++i) {
		printf("%d ", *(a+i));
	}
	printf("\n交换最大值之后的数组 b 的5个数字为:");
	for (int j = 0; j < N; ++j) {
		printf("%d ", *(b+j));
	}
}

示例结果:

$ gcc ex026.c -o demo
yaojianguodeMacBook-Pro:C语言100题集合代码 yaojianguo$ ./demo
输入数组 a 的5个数字:
1
3
5
7
9
输入数组 b 的5个数字:
2
4
6
8
10

数组 a 的5个数字为: 1 3 5 7 9
数组 b 的5个数字为: 2 4 6 8 10
数组 a 的最大值:9
数组 b 的最大值:10
交换最大值之后的数组 a 的5个数字为:1 3 5 7 10
交换最大值之后的数组 b 的5个数字为:2 4 6 8 9

案例ex27: 输出二维数组有关值(二维数组的经典案例,非常重要)

1 题目

功能:输出二维数组有关值(二维数组的经典案例,非常重要)

描述:输出二维数组中的有关值,以及指向二维数组的指针变量的应用

2 思路

非常重要!非常重要!非常重要!

a+1 是二维数组 a 中序号为 1 的行的首地址(序号从0起算),而*(a+1) 并不是 a+1 单元的内容(值),因为 a+1 并不是一个变量的存储单元,也就谈不上他的内容了。*(a+1) 就是 a[1],而 a[1] 是一维数组名,所以也是地址,它指向 a[1][0]。a[1] 和 *(a+1) 都是二维数组中地址的不同表现形式

3 代码

#include <stdio.h>

/**
功能:输出二维数组有关值(二维数组的经典案例,非常重要)
描述:输出二维数组中的有关值,以及指向二维数组的指针变量的应用
**/


int main(int argc,  char const *argv[]) {
    int a[3][4]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
    printf("%d, %d\n", a, *a); 								// 0 行的首地址, 0 行 0 列元素地址
    printf("%d, %d\n", a[0], *(a+0));					// 0 行 0 列元素地址, 0 行 0 列元素地址
    printf("%d, %d\n", &a[0], &a[0][0]);			// 0 行的首地址, 0 行 0 列元素地址
    printf("%d, %d\n", a[1], a+1);						// 1 行 0 列的元素地址, 1 行首地址
    printf("%d, %d\n", &a[1][0], *(a+1)+0);		// 1 行 0 列的元素地址, 1 行 0 列元素地址
    printf("%d, %d\n", a[1][1], *(*(a+1)+1));	// 1 行 1 列的值,1 行 1 列的值
    getchar();
}

示例结果:

$ gcc ex027.c -o demo
$ ./demo
-302746704,-302746704
-302746704,-302746704
-302746704,-302746704
-302746688,-302746688
-302746688,-302746688
6,6

案例ex28: 输出二维数组任一行任一列值

1 题目

功能:输出二维数组任一行任一列值

描述:一个 3 行 4 列的数组,输入要显示数组元素的所在行数和列数,将在终端显示该数组元素的值

2 思路

熟悉ex027的案例,对上一个案例的简单应用

要彻底理解指针和二维数组的操作以及其内在的联系

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:输出二维数组任一行任一列值
描述:一个 3 行 4 列的数组,输入要显示数组元素的所在行数和列数,将在终端显示该数组元素的值
**/

int main(int argc, char const *argv[]) {
    int a[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
    int *p, (*pt)[4], i, j;
    printf("数组内容为:");
    for (p = a[0]; p < a[0] + 12; p++) {
        if ((p - a[0]) % 4 == 0)
            printf("\n");
        printf("%4d",  *p);
    }
    printf("\n");
    printf("请输入想要获取的数字的位置: i=, j= \n");
    pt = a;
    scanf("i=%d, j=%d", &i, &j);

    printf("%d行%d列的值为;\na[%d, %d]=%d\n", i, j, i, j, *(*(pt + i) + j));
    getchar();
}

示例结果:

$ gcc ex028.c -o demo
$ ./demo
数组内容为:
   1   2   3   4
   5   6   7   8
   9  10  11  12
请输入想要获取的数字的位置: i=, j=
 i=1, j=2
1行2列的值为;
a[1, 2]=7

案例ex29: 将若干字符串按照字母顺序输出

1 题目

函数:sort()

功能:将若干字符串按照字母顺序输出

描述:实现对程序中几个字符串按照从小到大的顺序进行排序,并打印出来

2 思路

运用字符串数组的方式:

char * week[] = {};

将 week 传递到 sort() 函数,然后利用 C 语言提供的 strcmp() 进行字符类的比较

3 代码

#include <stdio.h>
#include <string.h>
#define N 7

/**
函数:sort()
功能:将若干字符串按照字母顺序输出
描述:实现对程序中几个字符串按照从小到大的顺序进行排序,并打印出来
**/


void sort(char *p[]) {
	char *temp;
	for (int i = 0; i < N; ++i) {
		for (int j = i+1; j < N; ++j) {
			if (strcmp(*(p+i), *(p+j)) > 0) {	// 比较大小,交换位置
				temp = *(p+i);
				*(p+i) = *(p+j);
				*(p+j) = temp;
			}
		}
	}
}


int main(int argc, char const *argv[]) {
	char * week[] = {
		"Monday",
		"Tuesday",
		"Wednesday",
		"Thursday",
		"Friday",
		"Saturday",
		"Sunday"
	};
	sort(week);
	printf("排序后的周为:\n");
	for (int i = 0; i < N; ++i) {
		printf("%s ", *(week+i));
	}
	getchar();
}

示例结果:

$ gcc ex029.c -o de
$ ./demo
排序后的周为:
Friday Monday Saturday Sunday Thursday Tuesday Wednesday

案例ex30: 用指向函数的指针比较大小

1 题目

函数:min() / max()

功能:用指向函数的指针比较大小

描述:实现输入两个整数后,打印较小和较大的那个数字(规定使用指向函数的指针实现)

2 思路

函数指针

  • 函数具有可赋值给指针的物理内存地址,一个函数的函数名就是一个指针,它指向函数的代码。一个函数的地址是该函数的进入点,也是调用函数的地址。函数的调用可以通过函数名,也可以通过指向函数的指针来调用。函数指针还允许将函数作为变元传递给其他函数。

  • 不带括号和变量列表的函数名,这可以表示函数的地址,正如不带下标的数组名可以表示数组的首地址。

  • 定义形式:

    // 类型 (*指针变量名)(参数列表);
    // 例如:
    int (*p)(int i,int j);
  • p是一个指针,它指向一个函数,该函数有2个整形参数,返回类型为int。p首先和*结合,表明p是一个指针。然后再与()结合,表明它指向的是一个函数。指向函数的指针也称为函数指针。

本案例中,利用指向函数的指针,在不同情况同一个指针指向不同的函数实现不同的功能

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
函数:min() / max()
功能:用指向函数的指针比较大小
描述:实现输入两个整数后,打印较小的那个数字(规定使用指向函数的指针实现)
**/

int min(int a, int b) {
    if (a < b)
        return a;
    else
        return b;
}

int max(int a, int b) {
    if (a > b)
        return a;
    else
        return b;
}

int main(int argc, char const *argv[]) {
    int(*p)();
    int a, b, max_v, min_v;

    printf("请你输入两个数字(如:a, b): \n");
    scanf("%d, %d", &a, &b);
    // 取小值
    p = min;
    min_v = (*p)(a, b);
    printf("min=%d\n", min_v);
    // 取大值
    p = max;
    max_v = (*p)(a, b);
    printf("max=%d\n", max_v);
    getchar();
}

示例结果:

$ gcc ex030.c -o demo
$ ./demo
请你输入两个数字(如:a, b):
1,5
min=1
max=5

$ ./demo
请你输入两个数字(如:a, b):
10,50
min=1
max=50

**案例ex31: 字符串的匹配 **

1 题目

函数:match()

功能:字符串的匹配

描述:

本例实现两个字符串的匹配操作,即在第一个字符串中查找是否存在第二个字符串。

如果字符串完全匹配,则提示”匹配“,并显示第二个字符串在第一个字符串中的开始位置。

否则:提示”不匹配“

2 思路

本案例自定义 match(char *B, char *A) 函数进行匹配,使用循环进行每个字符进行比较,从而找出是否包含子串

3 代码

#include <stdio.h>
#include <string.h>


/**
函数:match()
功能:字符串的匹配	
描述:
本例实现两个字符串的匹配操作,即在第一个字符串中查找是否存在第二个字符串。
如果字符串完全匹配,则提示”匹配“,并显示第二个字符串在第一个字符串中的开始位置。
否则:提示”不匹配“
**/


int match(char *B, char *A) {
    int i, j, start = 0;
    int lastB = strlen(B) - 1;
    int lastA = strlen(A) - 1;
    int endmatch = lastA;
    for (j = 0; endmatch <= lastB; endmatch++, start++)
    {
        if (B[endmatch] == A[lastA])
            for (j = 0, i = start; j < lastA && B[i] == A[j];)
                i++, j++;
        if (j == lastA)
        {
            return (start + 1); /*成功  */
        }

    }
    if (endmatch > lastB)
    {
        printf("字符串不匹配!");
        return  - 1;
    }
}


int main(int argc, char const *argv[]) {
    char s[] = "Computational advertising ecology"; // 计算广告生态
    char t[] = "advertising";
    int p = match(s, t);
    if (p !=  - 1)
    {
        printf("匹配!\n");
        printf("匹配的开始位置在: %d", p);
    }
    printf("\n");
    getchar();
}

示例结果:

$ gcc ex031.c -o demo
$ ./demo
匹配!
匹配的开始位置在: 15

案例ex32: 使用malloc()函数分配内存

1 题目

功能:使用malloc()函数分配内存

描述:

要求创建一个结构体类型的指针,其中包含两个成员,一个是整型,另外一个是结构体指针

使用 malloc() 函数分配一个结构体的内存空间,然后给这两个成员赋值并显示

2 思路

malloc() 函数的语法格式如下:

void *malloc(unsigned int size);

该函数的作用是在内存中的动态存储区域动态分配指定长度的存储空间。该函数返回一个指针,然后指向所分配存储空间的起始地址。

如果返回值 0,那么表示没有成功申请到内存空间。函数类型为 void * ,表示返回的指针不指向任何类型。

本例中,使用 malloc() 函数申请返回指向结构体的指针,利用该指针可以进行结构体成员的赋值和取值操作

st s = (struct stu*)malloc(sizeof(struct stu));

3 代码

#include <stdio.h>
#include <stdlib.h>


/**
功能:使用malloc()函数分配内存
描述:
要求创建一个结构体类型的指针,其中包含两个成员,一个是整型,另外一个是结构体指针。
使用 malloc() 函数分配一个结构体的内存空间,然后给这两个成员赋值并显示
**/

typedef struct stu {
	int n;
	struct stu *next;	// 结构体成员,指针类型
}*st;

int main(int argc, char const *argv[]) {
	st s = (struct stu*)malloc(sizeof(struct stu)); 	// 开辟存储空间
	s->n = 1; 				// 成员 n 赋值
	s->next = NULL;			// 成员 next 赋值
	printf("成员的 n = %d, 成员 next = %d\n", s->n, s->next);
	return 0;
}

示例结果:

$ gcc ex032.c -o demo
$ ./demo
成员的 n = 1, 成员 next = 0

案例ex33: 调用calloc()函数动态分配内存存放若干数据

1 题目

功能:调用calloc()函数动态分配内存存放若干数据

返回值分配域的起始地址吗,如果分配失败返回 0

理解malloc() 和 calloc() 异同点

2 思路

  • calloc() 函数

    的语法格式如下:

    void * calloc(unsigned n, unsigned size);

    在内存中动态分配 n 个长度为 size 的连续内存空间数组,calloc() 函数会返回一个指针

    该指针指向动态分配的连续内存空间地址。

    当分配错误的时候,会返回 0

  • malloc() 和 calloc() 异同点

    • 共同点:

      都为了分配存储空间,

      它们返回的是 void * 类型,也就是说如果我们要为int或者其他类型的数据分配空间 必须显式强制转换

    • 不同点:

      malloc() 1个形参,因此如果是数组,必须由我们计算需要的字节总数作为形参传递

      用malloc只分配空间不初始化,也就是依然保留着这段内存里的数据,

      calloc() 2个形参,因此如果是数组,需要传递个数和数据类型

      而calloc则进行了初始化, calloc分配的空间全部初始化为0 ,这样就避免了可能的一些数据错误

3 代码

#include <stdio.h>
#include <stdlib.h>


/**
功能:调用calloc()函数动态分配内存存放若干数据
返回值分配域的起始地址吗,如果分配失败返回 0
**/


int main(int argc, char const *argv[]) {
	int n;
	int *p, *q;
	printf("输入数据的个数:\n");
	scanf("%d", &n);
	p = (int *)calloc(n, sizeof(int));	// 分配内存空间,并且会进行初始化
	printf("为 %d 个数据分配内存空间 \n", n);
	for (q = p; q < p+n; ++q) {
		scanf("%d", q);
	}

	printf("给 p 指向的一段内存空间存储的内容是:\n");
	for (int i = 0; i < n; ++i) {
		printf("%d ", *(p+i));
	}
	printf("\n");
}

示例结果:

$ gcc ex033.c -o demo
$ ./demo
输入数据的个数:
5
为 5 个数据分配内存空间
1
5
6
8
9
给 p 指向的一段内存空间存储的内容是:
1 5 6 8 9

案例ex34: 为具有 5 个数组元素的数组分配内存

1 题目

功能:为具有 5 个数组元素的数组分配内存

描述:为具有 5 个元素的数组动态分配内存,并赋值输出

2 思路

使用 calloc() 函数为数组的 5 个元素进行分配存储空间,然后进行赋值

3 代码

#include <stdio.h>
#include <stdlib.h>
#define N 5


/**
功能:为具有 5 个数组元素的数组分配内存
描述:为具有 5 个元素的数组动态分配内存,并赋值输出
**/


int main(int argc, char const *argv[]) {
	int * p;
	p = (int *)calloc(N, sizeof(int));
	printf("看使用 calloc() 函数后,初始化的内容:\n");
	for (int i = 0; i < N; ++i) {
		printf("%d ", *(p+i));
	}
	getchar();
	// 赋值
	for (int i = 0; i < N; ++i) {
		*(p+i) = i*3;
	}
	// 输出
	printf("初始化后的数组的内容:\n");
	for (int i = 0; i < N; ++i) {
		printf("%d ", *(p+i));
	}	
	printf("\n");
}

示例结果:

$ gcc ex034.c -o demo
$ ./demo
看使用 calloc() 函数后,初始化的内容:
0 0 0 0 0
初始化后的数组的内容:
0 3 6 9 12

案例ex35: 为二维数组动态分配内存

1 题目

功能:为二维数组动态分配内存

描述:为二维数组动态分配内存,然后输出并释放内存

2 思路

  • 在C语言中,一维数组是通过 malloc() 函数动态分配空间来实现的,动态的二维数组也能够通过malloc()函数动态分配空间来实现。实际上,C语言中没有二维数组,至少对二维数组没有直接的支持,取而代之的是“数组的数组”,二维数组能够看成是由指向数组的指针构成的数组。
  • 对于一个二维数组p[i][j],编译器通过公式 *(*(p+i)+j) 求出数组元素的值,其中,p+i表示计算行指针;*(p+i)表示具体的行,是指针,指向该行首元素地址 *(*(p+i)+j 表示得到具体元素的地址;*(*(p+i)+j)表示得到元素的值。基于这个原理,通过分配一个指针数组,再对指针数组的每一个元素分配空间实现动态的分配二维数组

案例中实现的步骤

1.定义二维指针

int **p; 		// 二维数组指针

2.分配行动态空间

p = (int **)malloc(sizeof(int *[M])); 	// 指向指针的指针

3.再为每一行的每一列进行动态分配

*(p+i) = (int *)malloc(sizeof(int[N]));

3 代码

#include <stdio.h>
#include <stdlib.h>
#define M 3
#define N 5

/**
功能:为二维数组动态分配内存
描述:为二维数组动态分配内存,然后输出并释放内存
**/

int main(int argc, char const *argv[]) {
	int **p; 		// 二维数组指针
	p = (int **)malloc(sizeof(int *[M])); 	// 指向指针的指针
	for (int i = 0; i < M; ++i) {
		*(p+i) = (int *)malloc(sizeof(int[N]));
		for (int j = 0; j < N; ++j) {
			*(*(p+i)+j) = i + j;
		}
	}

	// 输出内容
	printf("给二维数组分配空间后,内存的内容是:\n");
	for (int i = 0; i < M; ++i) {
		for (int j = 0; j < N; ++j) {
			printf("%d \t", *(*(p+i)+j));
		}
		putchar('\n');
	}
}

示例结果:

$ gcc ex035.c -o demo
$ ./demo
给二维数组分配空间后,内存的内容是:
0 	1 	2 	3 	4
1 	2 	3 	4 	5
2 	3 	4 	5 	6

案例ex36: 商品信息的动态存放

1 题目

功能:商品信息的动态存放

描述:

创建一个商品的结构体

动态分配一块内存区域,存放一个商品信息

2 思路

  • 定义一个商品信息的结构体类型,同时声明一个结构体类型的指针COMM
  • 调用 malloc() 函数分配空间,地址存放在指针变量 commodity 中
  • 利用指针变量访问该地址空间中的每个成员数据,并为成员赋值,主要要使用 "->" 去访问,例如:
COMM commodity = (struct commodity *) malloc(sizeof(struct commodity));
commodity->num = 1011;
commodity->name = "计算广告生态";
commodity->count = 10001;
commodity->price = 15000.1;

3 代码

#include <stdio.h>
#include <stdlib.h>


/**
功能:商品信息的动态存放
描述:创建一个商品的结构体, 动态分配一块内存区域,存放一个商品信息
**/


typedef struct commodity {	// 结构体定义
	int num; 									// 编号
	char *name; 							// 商品名称
	int count;  							// 商品数量
	double price; 						// 商品单价
}*COMM;


int main(int argc, char const *argv[]) {
	COMM commodity = (struct commodity *) malloc(sizeof(struct commodity));
	commodity->num = 1011;
	commodity->name = "计算广告生态";
	commodity->count = 10001;
	commodity->price = 15000.1;
	printf("编号:%d\n商品名称:%s\n商品数量:%d\n商品单价:%f\n",	
		commodity->num, commodity->name, commodity->count, commodity->price);
}

示例结果:

$ gcc ex036.c -o demo
$ ./demo
编号:1011
商品名称:计算广告生态
商品数量:10001
商品单价:15000.100000

案例ex37: 用不带参数的宏定义求平行四边形面积

1 题目

功能:用不带参数的宏定义求平行四边形面积

描述:利用不带参数的宏的形式(一般宏大写字母,以便与其他的操作符进行区别)

2 思路

不带参数的宏名定义如下:

#define 宏名 字符串

一般情况下 "#" 表示这是一条预处理命令,宏名是一个标识符,必须符合 C 语言规定

字符串可以是常数、表达式、格式字符串等

后面几节分别就这几种进行讲解

3 代码

#include <stdio.h>
#define A  8  // 定义宏,设置底边的长度
#define H  6  // 定义宏,设置高的长度

/**
函数:fun()
功能:用不带参数的宏定义求平行四边形面积
描述:利用不带参数的宏的形式(一般宏大写字母,以便与其他的操作符进行区别)
**/

int main(int argc, char const *argv[]) {
    int area;												// 存储平行四边形面积
    area = A * H;										// 计算平行四边形面积
    printf("面积 = %d\n", area);		 // 输出面积值
}

示例结果:

$ gcc ex037.c -o demo
$ ./demo
面积 = 48

案例ex38: 使用宏定义实现数组值的互换

1 题目

功能:使用宏定义实现数组值的互换

描述:定义一个宏 swap(a, b),以实现两个整数的交换,并利用它将一维数组 a 和 b 进行交换

2 思路

宏关于函数的运用

一般形式:

#define 宏名(参数表) 字符串函数定义
  • 对带参数的宏的展开只是将语句中的宏名后面括号内的实参字符串代替 #define 命令行中的形参
  • 在宏定义时,在宏名与带参数的括号之间不可以加空格,否则将空格以后的字符都作为替代字符串的一部分
  • 在带参宏定义中,形式参数不分配内存单元,因此不必作类型定义

3 代码

#include <stdio.h>
#define N 10
#define swap(a, b) {int temp; temp=a;a=b;b=temp;} // 宏swap(a,b)进行两个整数的交换

/**
功能:使用宏定义实现数组值的互换
描述:定义一个宏 swap(a, b),以实现两个整数的交换,并利用它将一维数组 a 和 b 进行交换
**/

int main(int argc, char const *argv[]) {
	int a[N], b[N];
	printf("请输入一个数组a:\n");
	for (int i = 0; i < N; ++i) {
		scanf("%d", a+i);
	}
	printf("请输入一个数组b:\n");
	for (int i = 0; i < N; ++i) {
		scanf("%d", b+i);
	}

	printf("数组a的内容是:\n");
	for (int i = 0; i < N; ++i) {
		printf("%d ", *(a+i));
	}
	printf("\n");
	printf("数组b的内容是:\n");
	for (int i = 0; i < N; ++i) {
		printf("%d ", *(b+i));
	}

	// 交换数组a 和 数组b的内容
	for (int i = 0; i < N; ++i) {
		swap(*(a+i), *(b+i));
	}

	printf("\n");
	printf("交换后数组a的内容是:\n");
	for (int i = 0; i < N; ++i) {
		printf("%d ", *(a+i));
	}
	printf("\n");
	printf("交换后数组b的内容是:\n");
	for (int i = 0; i < N; ++i) {
		printf("%d ", *(b+i));
	}
}

示例结果:

$ gcc ex038.c -o demo
$ ./demo
请输入一个数组a:
1
3
5
7
9
11
13
15
17
19
请输入一个数组b:
2
4
6
8
10
12
14
16
18
20
数组a的内容是:
1 3 5 7 9 11 13 15 17 19
数组b的内容是:
2 4 6 8 10 12 14 16 18 20
交换后数组a的内容是:
2 4 6 8 10 12 14 16 18 20
交换后数组b的内容是:
1 3 5 7 9 11 13 15 17 19

案例ex39: 编写头文件包含圆面积的计算公式

1 题目

功能:编写头文件包含圆面积的计算公式

描述:

计算圆的面积,宏定义存储在一个头文件中

输入半径就可以得到面积

2 思路

使用不同的文件需要包含不同的 #include 指令,包含两种格式

#include <文件名>
#include "文件名"
  • 需要注意的是,这两种格式的区别是

    • 用尖括号时,系统到存放C库函数头文件所在的目录中寻找要包含的文件,这种称为标准方式
    • 用双引号时,系统先在用户当前目录中寻找要包含的文件,若找不到,再到存放C库函数头文件所在的目录中寻找要包含的文件
  • 如果为调用库函数用 #include 命令来包含相关的头文件,则用尖括号,可以节省査找的时间

  • 如果要包含的是用户自己编写的文件,一般用双引号,用户自己编写的文件通常是在当前目录中

如果文件不在当前目录中,双引号可给出文件路径

3 代码

主函数代码:

#include <stdio.h>
#include "ex039_area.h"

/**
功能:编写头文件包含圆面积的计算公式
描述:
计算圆的面积,宏定义存储在一个头文件中
输入半径就可以得到面积
**/


int main(int argc, char const *argv[]) {
    float r;							// 定义园的半径
    printf("请输入半径:\n");
    scanf("%f",&r);
    printf("面积 =%.2f \n",area(r));	// 调用 ex039_area.h 中的 area函数
}

ex039_area.h:

#define PI 3.14
#define area(r) PI*(r)*(r)

示例结果:

$ gcc ex039.c -o demo
$ ./demo
请输入半径:
3
面积 =28.26
~~~~~~~~~~~~~~~~~~~~
$ ./demo
请输入半径:
10
面积 =314.00

案例ex40: 利用宏定义求偶数和

1 题目

功能:利用宏定义求偶数和

描述:

定义一个宏实现求 1~100 的偶数和

定义一个宏判断一个数是否为偶数

2 思路

1、先熟悉带参数的宏已经参数宏利用其它的宏定义

#define TRUE 1
#define FALSE 0
#define EVEN(x) (((x)%2==0)?TRUE:FALSE)

2、要点

在累加求和过程中需要不断判断数据是否为偶数,因此要创建带参数的宏

把判断偶数的过程定义为常量,由于C语言中不提供逻辑常量,因此自定义宏 TRUE 和 FALSE,表示1和0

因此,判断偶数的宏又可以演变为下面的形式:

3 代码

#include <stdio.h>
#define TRUE 1
#define FALSE 0
#define EVEN(x) (((x)%2==0)?TRUE:FALSE)

/**
功能:利用宏定义求偶数和
描述:
定义一个宏实现求 1~100 的偶数和
定义一个宏判断一个数是否为偶数
**/

int main(int argc, char const *argv[]) {
    int sum = 0;
    for(int i = 1; i <= 100; ++i) {
        if(EVEN(i))	
            sum+=i;	
    }
    printf("SUM = %d\n",sum);	
}

示例结果:

$ gcc ex040.c -o demo
$ ./demo
SUM = 2550

案例ex41: 利用文件包含设计输出模式

1 题目

功能:利用文件包含设计输出模式

描述:

在程序设计时需要很多输出格式,如整型、实型及字符型等,在编写稈序时会经常使用这些输出格式

如果经常书写这些格式会很繁琐,要求设计一个头文件,将经常使用的

输出模式都写进头文件中,方便编写代码

2 思路

本稈序中仅举一个简单的例子,将整型数据的输出写入到头文件中,并将这个头文件

命名为 ex041_format.h 声明整型数据并输出的形式如下:

#define INTEGER(d) printf("%4d\n",d)

3 代码

主函数:

#include <stdio.h>
#include "ex041_format.h"	

/**
功能:利用文件包含设计输出模式
描述:
在程序设计时需要很多输出格式,如整型、实型及字符型等,在编写稈序时会经常使用这些输出格式
如果经常书写这些格式会很繁琐,要求设计一个头文件,将经常使用的
输出模式都写进头文件中,方便编写代码
**/

int main(int argc, char const *argv[]) {
    int d;					
    printf("请输入一个整数:");	
    scanf("%d", &d);		
    INTEGER(d);		// 使用宏定义的函数
}

ex041_format.h

#define INTEGER(d) printf("计算结果: %4d\n",d)

示例结果:

$ gcc ex041.c -o demo
$ ./demo
请输入一个整数:10
计算结果:   10

案例ex42: 使用条件编译隐藏密码

1 题目

功能:使用条件编译隐藏密码

描述:一般输入密码时都会用拿号来替代,用以增强安全性。要求设置一个宏,规定宏体为

1,在正常情况下密码显示为审号的形式,在某些特殊的时候,显示为字符串。运行结果

2 思路

C 语言预编译命令 #if··· #else··· #endif

这个方法一般可以用来调试的时候用,也可以作为控制语句进行使用。有时候串口打印信息太多,一条条注释就很麻烦,于是就用这种方法,定义个宏变量,判断宏变量的条件,来达到改变宏变量的值控制那些代码编译

指令格式为:

#if
	语句段1
#else
	语句段2
#endif

对于一个字符串要求有两种输出形式

一种是原样输出

另一种是用相同数目输出

可以通过选择语句来实现,但是使用条件编译指令可以在编译阶段就决定要怎样操作

3 代码

#include <stdio.h>
#define PWD 1

/**
功能:使用条件编译隐藏密码
描述:一般输入密码时都会用拿号来替代,用以增强安全性。要求设置一个宏,规定宏体为
1,在正常情况下密码显示为审号的形式,在某些特殊的时候,显示为字符串。运行结果
**/

int main(int argc, char const *argv[]) {
    char *s="mrsoft";
#if PWD	
    printf("******\n");
#else					
    printf("%s\n",s);	
#endif
}

示例结果:

$ gcc ex042.c -o demo
$ ./demo
******

案例ex43: 关闭所有打开的文件

1 题目

功能:用fgetc函数从键盘逐个输入字符,然后用fputc函数写到磁盘文件中

描述:

用 fgetc 函数从键盘逐个输入字符,然后用 fputc函数写到磁盘文件即可

2 思路

  1. 用来存储数据的文件名可以在fopen函数中直接写成字符串常量形式(如指定"1"),也可以在程序运行时由用户临时指定。本程序采取的方法是由键盘输入文件名。为此设立一个字符数组filename,用来存放文件名。运行时,从键盘输入磁盘文件名"ex043_file.dat", 操作系统就新建立一个磁盘文件ex043_file.dat,用来接收程序输出的数据
  2. 用fopen函数打开一个"只写"的文件("w"表示只能写入不能从中读数据),如果打开文件成功,函数的返回值是该文件所建立的信息区的起始地址,把它赋给指针变量fp(fp已定义为指向文件的指针变量)。如果不能成功地打开文件,则在显示器的屏幕上显示"无法打开此文件",然后用exit函数终止程序运行
  3. exit是标准C的库函数,作用是使程序终止,用此函数时在程序的开头应包含 <stdlib.h> 头文件
  4. 用getchar函数接收用户从键盘输入的字符。注意每次只能接收一个字符。今输入字符串"公众号:计算广告生态"是用来向程序表示:输入的字符串到此结束。用什么字符作为结束标志是人为的,由程序指定的,也可以用别的字符(如或其他字符)作为结束标志。但应注意:如果字符串中包含"#",就不能用"# "作结束标志
  5. 执行过程是:先从键盘读入一个字符,检查它是否如果是,表示字符串已结束,不执行循环体。如果不是'#',则执行一次循环体,将该字符输出到磁盘文件filel.datₒ然后在屏幕上显示出该字符,接着再从键盘读入一个字符。如此反复,直到读入'#'字符为止。这时,程序已将"公众号:计算广告生态"写到以"filel. dat"命名的磁盘文件中了,同时在屏幕上也显示出了这些字符,以便核对。
  6. ex043_file.dat 中是否确实存储了这些内容,可以在资源管理器中,按记事本的打开方式打开文件,或者在其他系统有其他查看方式

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:关闭所有打开的文件
描述:
用 fgetc 函数从键盘逐个输入字符,然后用 fputc函数写到磁盘文件即可
**/

int main(int argc, char const *argv[]) {
	FILE * fp;
	char ch, filename[10];
	printf("请输入要操作的文件名:\n");
	scanf("%s", filename);

	if ((fp = fopen(filename, "w")) == NULL) { 	// 打开输入文件并使 fp 指向此文件	
		printf("无法打开此文件!\n");
		exit(0); 										// 终止程序
	}
	ch = getchar();								// 用来接收最后输入的回车符
	printf("请输入一个准备存储到磁盘的字符串(以'#'结束)\n");
	ch = getchar();								// 用来接收从键盘输入的第一个字符
	while (ch != '#') {						// 当输入"#"时结束循环
		fputc(ch, fp);							// 用来向磁盘输出第一个字符
		putchar(ch);								// 将输出的字符显示在屏幕上
		ch = getchar();							// 接收从键盘输入的一个字符
	}
	fclose(fp);										// 关闭文件
	putchar(10);									// 向屏幕输出一个换行符
	return 0;
}

示例结果:

$ gcc ex043.c -o demo
$ ./demo
请输入要操作的文件名:
ex043_file.dat
请输入一个准备存储到磁盘的字符串(以'#'结束)
公众号:计算广告生态
公众号:计算广告生态
#

文件中的显示:

ex043_file.dat:

公众号:计算广告生态

案例ex44: 将文件中的内容从一个文件拷贝到另外一个文件

1 题目

功能:将文件中的内容从一个文件拷贝到另外一个文件

描述:将上一个案例中的ex043_file.dat 赋值内容到 ex044_file.data

2 思路

1、在访问磁盘文件时,是 逐个字符(字节) 进行的,为了知道当前访问到第几个字节,系统用 " 文件读写位置标记 " 来表示当前所访问的位置。开始时“文件读写位置标记”指向第1个字节, 每访问完一个字节后,当前读写位置就指向下一个字节 ,即当前读写位置自动后移。

2、为了知道对文件的访问是否完成,只须看文件读写位置是否移到文件的末尾。用 feof函数 可以检查到" 文件读写位置标记 "是否移到 文件的末尾 ,即磁盘文件是否结束。程序第 26 行中的feof(in)是检查in所指向的文件是否结束。如果是,则函数值为1(真),否则为0(假),也就是“ !feof(in) ”为真,在while循环中检査“!feof(in)”为真,就执行循环体。

3、运行结果是将file.dat文件中的内容复制到file2.dat中去。打开这两个文件,可以看到

filel. dat 和 file2. dat 的内容都是:

This is the first message
copy to another file

4、以上程序是按文本文件方式处理的。也可以用此程序来复制一个二进制文件,只须将两个fopen函数中的“r”和“w”分别改为“rb”和“wb”即可。

5、C 系统已把fputc和fgetc函数定义为宏名putc和getc:

# define putc(ch, fp) fputc(ch, fp)
# define getc(fp) fgetc(fp)

这是在 <stdio.h> 中定义的。因此,在程序中用putc和fputc作用是一样的,用getc和fgetc

作用是一样的。在使用的形式上,可以把它们当作相同的函数对待。

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:将文件中的内容从一个文件拷贝到另外一个文件
描述:将上一个案例中的ex043_file.dat 赋值内容到 ex044_file.dat
**/

int main(int argc, char const *argv[]) {
	FILE *in, *out;										// 指向文件的变量
	char ch, infile[20], outfile[20];	// 定义的两个字符数组,分别存放两个数据文件名
	printf("输入读入文件的名称(ex043_file.dat):");
	scanf("%s", infile);							// 输入第一个要读取文件的文件名
	printf("输入输出文件的名称(ex044_file.dat):");
	scanf("%s", outfile);							// 输入第一个要读取文件的文件名
	
	if ((in = fopen(infile, "r"))==NULL) {	 // 打卡输入文件
		printf("无法打开输入文件..\n");
		exit(0);
	}
	if ((out = fopen(outfile, "w"))==NULL) {	 // 打卡输出文件
		printf("无法打开输出文件..\n");
		exit(0);
	}	
 	
	while(!feof(in)) {					// 如果未遇到输入文件的结束标志,每次访问完一个字节后自动指向下一个字节
		ch = fgetc(in);						// 从输入文件中读取一个
		fputc(ch, out);						// 将 ch 写到 outfile 中
		putchar(ch); 							// 显示到屏幕上
	}
	putchar(10);								// 最后进行换行
	fclose(in);									// 关闭输入文件
	fclose(out);								// 关闭输出文件
	return 0;
}

示例结果:

$ gcc ex044.c -o demo
$ ./demo
输入读入文件的名称(ex043_file.dat):ex043_file.dat
输入输出文件的名称(ex044_file.dat):ex044_file.dat
This is the first message
copy to another file

原来文件中的内容:

This is the first message  copy to another file

赋值过去的文件内容:

This is the first message
copy to another file

案例ex45: 将排好序的字符串保存到磁盘文件中

1 题目

功能:将排好序的字符串保存到磁盘文件中

描述:从键盘读入若干个字符串,对它们按字母大小的顺序排序,然后把排好序的字符串送到磁盘文件中保存

2 知识点

2.1 处理字符串的两个函数

函数名 调用形式 功能 返回值
fgets fgets(str, n, fp) 从fp指向的文件读入一个长度为 (n-1) 的字符串,存放到字符数组str中 读成功,返回地址str,失败则返回 NULL
fputs fputs(str, fp) 把str所指向的字符串写到文件指针变量 fp 所指向的文件中 输出成功,返回0;否则返回非 0 值

fgets 中最后一个字母 s 表示字符串(string)。fgets 的含义是,从文件读取一个字符串

1. fgets 函数的函数原型

char * fgets(char * str, int n, fILE * fp);

其作用是从文件中读取一个字符串。调用时可以写成

fgets(str, n, fp)

其中 n 是要求得到的字符的个数, 但实际上只从fp所指向的文件中读取 n-1 个字符,然后在最后加 '\0' 字符 ,这样得到的字符串共有 n 个字符,把它们放到数组 str 中。如果在读完 n-1 个字符之前遇到了换行符 '\n' 或者结束符EOF,读入即结束,这块遇到换行符 '\n' 也作为一个字符读入。若执行 fgets函数成功,则返回值为 str 数组元素的地址,如果一开始就遇到文件尾或者读取数据出错,则返回 NULL

2.fputs函数的函数原型

char * fputs(char * str, fILE * fp);

其作用是将str所指向的字符串输出到fp所指向的文件中。调用时可以写成

例如:

fputs("China", fp);

把字符串“China”输出到fp指向的文件中。fputs函数中第 1 个参数可以是字符串常量、字符数组名或者字符型指针。字符串末尾的 '\0' 不输出,若输出成功,函数值为 0;失败时,函数值为 EOF。

fgets 和 fgets 这两个函数的功能类似于 gets 和 puts 函数,只是 gets 和 puts 以终端为读写对象,而 fgets 和 fputs 函数以指定的文件作为读写对象。

3 思路

  1. 从键盘读入n个字符串,存放在一个二维字符数组中,每个一维数组存放一个字符串
  2. 对字符数组中的n个字符串按字母顺序排序,排好序的字符串仍存放在字符数组中
  3. 将字符数组中的字符串顺序输出

4 代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/**
功能:将排好序的字符串保存到磁盘文件中
描述:从键盘读入若干个字符串,对它们按字母大小的顺序排序,然后把排好序的字符串送到磁盘文件中保存
**/

int main(int argc, char const *argv[]) {
	FILE * fp;
	char str[3][20], temp[10];				// str保存存放字符串的二维数组,temp 临时数组
	int i, j, k, n = 3;
	printf("输入三个字符串: \n");				// 提示输入字符串
	for (i = 0; i < n; ++i) {
		gets(str[i]); 										// 终端输入
	}


	for(i=0; i<n-1; i++) {         			// 用选择法对字符串排序
		k=i;
		for(j=i+1;j<n;j++)
			if(strcmp(str[k], str[j]) > 0)
				k=j;
		if(k != i) {
			strcpy(temp, str[i]);
			strcpy(str[i], str[k]);
			strcpy(str[k], temp);
		}
	}

	if ((fp = fopen("save_string.dat", "w")) == NULL) {	 // 打开磁盘文件
		printf("打开文件失败\n");
	}

	printf("新的语句是:\n");
	for (i = 0; i < n; ++i) {
		fputs(str[i], fp);
		fputs("\n", fp);
		printf("%s\n", str[i]);
	}
}

示例结果:

$ gcc ex045.c -o demo
$ ./demo
输入三个字符串:
grasp
apple
banana
新的语句是:
apple
banana
grasp

案例ex46: 用二进制方式向文件读写一组数据

1 题目

函数:save()/read()

功能:用二进制方式向文件读写一组数据

描述:从键盘输入 10 个学生的有关数据,然后把他们转存到磁盘文件中去

2 知识点

在程序中不仅需要一次输入输出一个数据, 而且常常需要一次输入输出一组数据(如数组或结构体变量的值), C语言允许用fread函数从文件中读一个数据块, 用fwrite函数向文件写一个数据块。在读写时是以二进制形式进行的。向磁盘写数据时, 直接将内存中一组数据原封不动、不加转换地复制到磁盘文件上, 在读入时也是将磁盘文件中若干字节的内容一批读入内存

它们的一般调用形式为:

fread(buffer, size, count, fp);
fwrite(buffer, size, count, fp);

buffer:是一个地址

对fread来说,它是用来存放从文件读入的数据的存储区的地址

对fwrite来说,是要把此地址开始的存储区中的数据向文件输出(以上指的是起始地址)

size:要读写的字节数

count:要读写多少个数据项(每个数据项长度为size)

fp: FILE类型指针

在打开文件时指定用二进制文件, 这样就可以用 fread 和 fwrite 函数读写任何类型的信息, 例如:

fread(f, 4, 10, fp);

其中 f 是一个 float 型数组名(代表数组首元素地址)。这个函数从 fp 所指向的文件读入10个4字节的数据,存储到数组 f 中。

3 思路

定义一个有 10 个元素的结构体数据,用来存放 10 个学生的数据。

从 main() 中输入 10 个数据。

用 save() 函数实现向磁盘输出学生数据。

用 fwrite() 函数一次输出一个学生的数据

用fread() 函数进行数据的读取

  1. 在main函数中,从终端键盘输入io个学生的数据,然后调用 save 函数,将这些数据输出到以“stu.dat”命名的磁盘文件中。fwrite函数的作用是将一个长度为36节的数据块送到 ex046_stud.dat 文件中(一个struct student_type类型结构体变量的长度为它的成员长度之和,即10 + 4 + 4 + 15 = 33,实际上占36字节,是4的倍数)
  2. 在fopen函数中指定读写方式为“wb”,即二进制写方式。在向磁盘文件 ex046_stud.dat 写的时候,将内存中存放stud数组元素stud订的内存单元中的内容原样复制到磁盘文件,所建立的 ex046_stud.dat 文件是一个二进制文件。这个文件可以为其他程序所用。
  3. 在本程序中,用fopen函数打开文件时没有指定路径,只写了文件名ex046_stud.dat,系统默认其路径为当前用户所使用的子目录,在此目录下建立一个新文件ex046_stud.dat,输出的数据存放在此文件中。
  4. 程序运行时,屏幕上并无输出任何信息,只是将从键盘输入的数据送到磁盘文件上

4 代码

#include <stdio.h>
#include <stdlib.h>
#define SIZE 10

/**
函数:save()/read()
功能:用二进制方式向文件读写一组数据
描述:从键盘输入 10 个学生的有关数据,然后把他们转存到磁盘文件中去
思路:
**/

struct Student_type {
	char name[10];
	int num;
	int age;
	char addr[30];
}stud[SIZE];

// 以二进制格式进行保存
void save() {
	FILE * fp;
	if ((fp = fopen("ex046_stud.dat", "wb")) == NULL) {		// 打卡输入文件 ex046_stud.dat
		printf("写入文件打开失败!\n");
		return;
	} else {
		printf("写入成功!\n");
	}
	for (int i = 0; i < SIZE; ++i) {
		if(fwrite(&stud[i], sizeof(struct Student_type), 1, fp) != 1)
			printf("写入失败\n");
	}
	fclose(fp);
}

// 从二进制文件中读取数据
void read() {
	FILE * fp;
	if ((fp = fopen("ex046_stud.dat", "rb")) == NULL) {
		printf("读取打开文件失败!\n");
		exit(0);
	}
	for (int i = 0; i < SIZE; ++i) {
		fread(&stud[i], sizeof(struct Student_type), 1, fp); 	// 从 fp 指向的文件读入一组数据
		printf("%s\t%d\t%d\t%s\n", stud[i].name, stud[i].num, stud[i].age, stud[i].addr);
	}
	fclose(fp);
}

int main(int argc, char const *argv[]) {
	printf("输入 %d 个学生的信息:\n", SIZE);
	for (int i = 0; i < SIZE; ++i) {	// 注意 scanf 中的地址传递
		scanf("%s%d%d%s",stud[i].name,&stud[i].num,&stud[i].age,&stud[i].addr);
	}
	save();
	printf("\n====== 读取写入的数据 =======\n");
	read();
	return 0;
}

示例结果:

$ gcc ex046.c -o demo
$ ./demo
输入 10 个学生的信息:
"A" 1 11 "street01"
"B" 2 12 "street02"
"C" 3 13 "street03"
"D" 4 14 "street04"
"E" 5 15 "street05"
"F" 6 16 "street06"
"G" 7 17 "street07"
"H" 8 18 "street08"
"I" 9 19 "street09"
"J" 10 110 "street010"
写入成功!

====== 读取写入的数据 =======
"A"	1	11	"street01"
"B"	2	12	"street02"
"C"	3	13	"street03"
"D"	4	14	"street04"
"E"	5	15	"street05"
"F"	6	16	"street06"
"G"	7	17	"street07"
"H"	8	18	"street08"
"I"	9	19	"street09"
"J"	10	110	"street010"

案例ex47: 对一个文件重复读取

1 题目

功能:对一个文件重复读取(利用rewind()函数)

描述:

将磁盘文件的内容进行读取

第一次将它的内容显示在屏幕上,第二次把他复制到另外一个文件中

2 知识点

对文件需要进行随机读取的原因:

对文件进行顺序读写比较容易理解,也容易操作,但有时效率不高,例如文件中有1000个数据,若只査第1000个数据,必须先逐个读入前面999个数据,才能读入第1000个数据。如果文件中存放一个城市几百万人的资料,若按此方法查某一人的情况,等待的时间可能太长了。

随机访问不是按数据在文件中的物理位置次序进行读写,而是可以对任何位置上的数

据进行访问,显然这种方法比顺序访问效率高得多。

rewind() 函数

文件位置标记定位,强制使文件位置标记指向文件开头,此函数没有返回值

3 思路

可以利用 rewind() 函数进行操作,当第一次读取完后,文件位置标记指向了文件尾,那么使用 rewind() 函数将文件位置标记重新返回到文件头部

4 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:对一个文件重复读取
描述:将磁盘文件的内容进行读取,第一次将它的内容显示在屏幕上,第二次把他复制到另外一个文件中
**/

int main(int argc, char const *argv[]) {
	FILE * fp1, * fp2;
	fp1 = fopen("ex047_1.dat", "r"); 	// 打开输入文件
	fp2 = fopen("ex047_2.dat", "w"); 	// 打开写入文件
	while(!feof(fp1)){
		putchar(fgetc(fp1)); 			// 输出到屏幕
	}
	putchar(10); 						// 换行
	rewind(fp1);						// 文件位置标记重新返回文件的开头
	while(!feof(fp1)) {
		fputc(fgetc(fp1), fp2); 			// 重头,逐个字符读取写入到ex047_2.dat
	}
	fclose(fp1);
	fclose(fp2);
	return 0;
}

执行后结果:

$ gcc ex047.c -o demo
$ ./demo
hello
world
this is my first use of wind!

ex047_2.dat 文件中内容,与ex047_1.dat一致:

hello
world
this is my first use of wind!

案例ex48: 将文件中不同位置的数据打印出来

1 题目

功能:将文件中不同位置的数据打印出来

描述:

在磁盘上也存有10个学生的数据,要求将第 1、3、5、7、9 的学生数据输入到计算机

并且在屏幕中显示出来

2 知识点

  • fseek() 函数

    fseek() 函数改变文件位置标记

    fseek() 的调用形式;

    fseek(文件类型指针, 位移量, 起始点)

    起始点用0,1或者2来替代。0 代表文件起始位置、1代表当前位置、2代表文件末尾位置

    "位移量"指的是一“起始点” 为基点,向前移动的字节数。位移量应是 long 型数据

    fseek() 函数一般用于二进制文件。看以下例子:

    fseek(fp, 100L, 0) 	// 将文件位置标记向前移动到离文件开头100个字节处
    fseek(fp, 50L, 1) 	// 将文件位置标记向前移动到离当前位置50个字节处
    fseek(fp, -10L, 2) 	// 将文件位置标记向前移动到离文件末尾10个字节处
  • ftell() 函数

    测定文件位置标记的当前位置

    ftell() 的作用是得到流式文件中文件位置标记的当前位置

    由于文件中的文件位置标记经常移动,人们往往不容易知道其当前的位置,所以常用 ftell() 函数得到当前位置, 用相对于文件开头的位移量来表示

    如果调用函数时出错(例如不存在 fp 指向的文件), ftell() 返回 -1L。如下:

    i = ftell(fp); 	// i 存放文件当前的位置
    if (i == -1L) {
    	print("error!");
    }

3 思路

  1. 按照2进制只读的方式打开指定文件,准备从文件中读取学生的成绩
  2. 将文件位置标记指向文件的开头,然后从磁盘文件读取一个学生的信息,并把它显示在屏幕上
  3. 再将文件位置标记指向第 1、3、5、7、9 的学生的数据区的开头,从磁盘中读取相应的学生的信息,显示在屏幕上
  4. 关闭文件

注意:将【ex046.c 用二进制方式向文件读写一组数据】按照二进制形式写入的学生信息进行读取

4 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:将文件中不同位置的数据打印出来
描述:
在磁盘上也存有10个学生的数据,要求将第 3、5、7、9 的学生数据输入到计算机
并且在屏幕中显示出来
**/

struct student_type { 	// 学生数据信息
	char name[10];
	int num;
	int age;
	char addr[30];
}stud[10];


int main(int argc, char const *argv[]) {
	FILE * fp;
	if((fp = fopen("ex046_stud.dat", "rb")) == NULL) {
		printf("打开文件失败!\n");
		exit(0);
	}
	for (int i = 0; i < 10; i+=2) {
		fseek(fp, i*sizeof(struct student_type), 0); 			// 移动文件位置标记
		fread(&stud[i], sizeof(struct student_type), 1, fp);	// 读取一个数据块到结构体变量
		printf("%s %4d %d %s \n", stud[i].name, stud[i].num, stud[i].age, stud[i].addr);
	}
}

源文件中的数据:

"A"	1	11	"street01"
"B"	2	12	"street02"
"C"	3	13	"street03"
"D"	4	14	"street04"
"E"	5	15	"street05"
"F"	6	16	"street06"
"G"	7	17	"street07"
"H"	8	18	"street08"
"I"	9	19	"street09"
"J"	10	110	"street010"

示例结果:

"A"    1 11 "street01"
"C"    3 13 "street03"
"E"    5 15 "street05"
"G"    7 17 "street07"
"I"    9 19 "street09"

案例ex49: 文件加密

1 题目

函数:encrypt()

功能:文件加密

描述:C语言实现文件加密,执行参数有

【原文件路径及名称 密码 加密后文件路径及名称】

2 思路

register 关键字

C语言中的关键字register代表寄存器存储类型

register修饰符暗示编译程序相应dao的变量zhuan将被频繁地使用,如shu果可能的话,应将其保存在CPU的寄存器中,以加快其存储速度。这个关键字请求编译器尽可能的将变量存在CPU内部寄存器中而不是通过内存寻址访问以提高效率

加密

ch = ch ^ *(pwd + i);

采用异或方法进行加密,当然不同的情况可以采用不同的方式进行加密

3 代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/**
函数:fun()
功能:文件加密
描述:C语言实现文件加密,执行参数有
【原文件路径及名称 密码 加密后文件路径及名称】
**/



void encrypt(char *s_file, char *pwd, char *c_file);			     	// 文件加密函数
void encrypt(char *s_file, char *pwd, char *c_file) {  					// 自定义函数encrypt用于加密
    int i = 0;
    FILE *fp1,  *fp2; 													// 定义fp1和fp2是指向结构体变量的指针
    register char ch;
    fp1 = fopen(s_file, "rb");
    if (fp1 == NULL) {
        printf("无法打开原文件.\n");
        exit(1); 														// 如果不能打开要加密的文件,便退出程序
    }
    fp2 = fopen(c_file, "wb");
    if (fp2 == NULL) {
        printf("无法打开被加密的文件.\n");
        exit(1); 														// 如果不能建立加密后的文件,便退出
    }
    ch = fgetc(fp1);
    while (!feof(fp1))	{ 												// 测试文件是否结束

        ch = ch ^ *(pwd + i);											// 采用异或方法进行加密,也可以采用其他方式方法就行加密
        i++;
        fputc(ch, fp2);													// 异或后写入fp2文件
        ch = fgetc(fp1);
        if (i > 9)
            i = 0;
    }
    fclose(fp1);
    fclose(fp2);
}

int main(int argc, char const *argv[]) {
    char sourcefile[50]; 												// 被加密的文件名称
    char codefile[50];                         // 加密后的文件名
    char pwd[10]; 														// 密码存储
    if (argc != 4) {													// 容错处理,不满足参数格式,需要自行输入
        printf("请输入原文件名称:\n");
        gets(sourcefile); 												// 得到要加密的文件名
        printf("请输入密码:\n");
        gets(pwd); 														// 得到密码
        printf("请输入加密后文件名称:\n");
        gets(codefile); 												// 得到加密后你要的文件名
        encrypt(sourcefile, pwd, codefile);
    }
    else {
        strcpy(sourcefile, argv[1]);
        strcpy(pwd, argv[2]);
        strcpy(codefile, argv[3]);
        encrypt(sourcefile, pwd, codefile);
    }
}

示例结果:

$ gcc ex049.c -o demo
$ ./demo
请输入原文件名称:
s_file.txt
请输入密码:
johngo_py
请输入加密后文件名称:
c_file.txt

最后就可以发现有加密后的文件产出

案例ex50: 使用共用体存放学生和老师的信息

1 题目

功能:使用共用体存放学生和老师的信息

描述:根据输入职业的标识,区分出是老师还是学生

然后根据输入的标识将对应的信息输出。

如果是学生,则输出班级信息

如果是老师,则输出职位信息

2 思路

共用体有时也被称为联合或者联合体

这也是 Union 这个单词的本意

结构体和共用体的区别:

结构体的各个成员会占用不同的内存,互相之间没有影响;

共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。

结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙),共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。

共用体也是一种自定义类型,可以通过它来创建变量,例如:

union data{
	int n;
	char ch;
	double f;
};
union data a, b, c;

上面是先定义共用体,再创建变量,也可以在定义共用体的同时创建变量:

union data{
	int n;
	char ch;
	double f;
} a, b, c;

如果不再定义新的变量,也可以将共用体的名字省略:

union{
	int n;    	
	char ch;
	double f;
} a, b, c;

另外,看看下面代码中对共用体的使用方法,可以在后面编程中参考使用

3 代码

#include <stdio.h>
#include <stdlib.h>
#define N 2

/**
函数:fun()
功能:使用共用体存放学生和老师的信息
描述:根据输入职业的标识,区分出是老师还是学生
然后根据输入的标识将对应的信息输出。
如果是学生,则输出班级信息
如果是老师,则输出职位信息
**/


struct {
	int num;
	char name[10];
	char tp;
	union {									//共用体类型
		int inclass;
		char position[10];
	}job;									  //共用体变量
}person[N];									//结构体变量

int main(int argc, char const *argv[]) {
	int i;
	printf("请输入 %d 个人信息(编号 姓名 类型(s/t) 班级/职位):\n", N);
	// 信息填写
	for(i=0; i<N; i++) {
		printf("第%d个人\n",i+1);
		scanf("%d %s %c", &person[i].num, person[i].name, &person[i].tp);		/*输入信息*/
		if(person[i].tp=='s')										/*根据类型值判断是老师还是学生*/
			scanf("%d", &person[i].job.inclass);					/*输入工作类型*/
		else if(person[i].tp=='t')
			scanf("%s", person[i].job.position);
		else
			printf("输入有误");
	}
	printf("\n编号    姓名    类型    班级/职位\n");
	
	// 信息输出
	for(i=0;i<2;i++) {
		if(person[i].tp == 's')										/*根据工作类型输出结果*/
			printf("%d\t%s\t%c\t%d", person[i].num, person[i].name, person[i].tp, person[i].job.inclass);
		else if(person[i].tp == 't')
			printf("%d\t%s\t%c\t%s", person[i].num, person[i].name, person[i].tp, person[i].job.position);
		printf("\n");			
	}
}

示例结果:

$ gcc ex050.c -o demo
$ ./demo
请输入 2 个人信息(编号 姓名 类型(s/t) 班级/职位):
第1个人
1 Johngo1 s 1
第2个人
2 Johngo2 t 数学

编号    姓名    类型    班级/职位
1	Johngo	s	1
2	Johngo2	t	数学

案例ex51: 使用共用体处理任意类型数据

1 题目

功能:使用共用体处理任意类型数据

描述:设计一个共用体类型,使其成员包含多种数据类型,根据不同的类型,输出不同的数据

2 思路

首先设定了各种数据类型的变量,由于这些变量不是全部一次性处理的,所以就采用了共用体类型

在下面例子中,通过 TypeFlag 来识别在共用体重的存储类型,执行程序的时候就会按照不同的存储方式进行存储到 union_demo 变量中

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
函数:fun()
功能:使用共用体处理任意类型数据
描述:设计一个共用体类型,使其成员包含多种数据类型,根据不同的类型,输出不同的数据
**/

union {										// 定义共用体
	int i;									// 共用体成员
	char c;
	float f;
	double d;
}union_demo;										// 声明共用体类型的变量

int main(int argc, char const *argv[]) {
	char TypeFlag;
	printf("输入成员类型:\n");
	scanf("%c",&TypeFlag);								// 输入类型符
	printf("输入数字:\n");
	switch(TypeFlag) {									// 多分支选择语句判断输入
		case 'i':scanf("%d",&union_demo.i); break;
		case 'c':scanf("%c",&union_demo.c); break;
		case 'f':scanf("%f",&union_demo.f); break;
		case 'd':scanf("%lf",&union_demo.d);
	}
	switch(TypeFlag) { 	 								// 多分支选择语句判断输出
		case 'i':printf("%d",union_demo.i); break;
		case 'c':printf("%c",union_demo.c); break;
		case 'f':printf("%f",union_demo.f); break;
		case 'd':printf("%lf",union_demo.d);
	}							
	printf("\n");
}

示例结果:

$ gcc ex051.c -o demo
$ ./demo
输入成员类型:
i
输入数字:
10
10

案例ex52: 输出今天星期几

1 题目

2 思路

举语法定义格式为:

enum 枚举名 {枚举元素1,枚举元素2,……};

举个例子,比如:一星期有 7 天,如果不用枚举,我们需要使用 #define 来为每个整数定义一个别名:

#define Monday  		1
#define Tuesday  		2
#define Wednesday  	3
#define Thursday  	4
#define Friday  		5
#define Saturday  	6
#define Sunday  		7

这个看起来代码量就比较多,接下来我们看看使用枚举的方式:

enum week{Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday} ;

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
函数:week()
功能:输出今天星期几
描述:枚举类型的使用,利用枚举类型表示一周的每一天,然后通过数据来输出对应周几
**/

enum week{Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday} ; //定义枚举结构

int main(int argc, char const *argv[]) {
	int day;											//定义整型变量
	printf("输入星期数(0-6):");
	scanf("%d",&day);									//输入0-6的值
	switch(day) {										//根据数值进行判断
		case Sunday: printf("今天是星期天"); break;			//根据枚举类型进行判断
		case Monday: printf("今天是星期一"); break;
		case Tuesday: printf("今天是星期二"); break;
		case Wednesday: printf("今天是星期三"); break;
		case Thursday: printf("今天是星期天四"); break;
		case Friday: printf("今天是星期五"); break;
		case Saturday: printf("今天是星期六"); break;	
	}
	printf("\n");
}

示例结果:

$ gcc ex052.c -o demo
$ ./demo
输入星期数(0-6):5
今天是星期五

案例ex53: 任意次方后的最后三位

1 题目

功能:任意次方后的最后三位

描述:

求一个整数任意次方后的最后三位数,即求 x^y 的最后三位

x 和 y 的值由键盘输入

2 思路

注意点:

为了防止计算越界,本例中不能使用直接计算一个数的任意次方。那么如何去计算并且有效防止计算越界,而且不会产生误差。

这里采用了一种方式就是在每次进行次方相乘后,取其后三位,这样就不会出现越界的现象产生。

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:任意次方后的最后三位
描述:
求一个整数任意次方后的最后三位数,即求 x^y 的最后三位
x 和 y 的值由键盘输入
**/

int main(int argc, char const *argv[]) {
	int i, x, y, z = 1;
	printf("请输入两个数,x和y(x^y):\n");
	scanf("%d%d", &x, &y); 								    // 输入底数和幂数
	for (i = 1; i <= y; i++)
		z = z * x % 1000;								    // 计算一个数任意次方的后三位
	if(z>=100)
		printf("%d^%d的最后三位是:%d\n", x, y, z); 			// 输出最终结果*/
	else
		printf("%d^%d的最后三位是:0%d\n", x, y, z);			// 输出最终结果
}

示例结果:

$ gcc ex053.c -o demo
$ ./demo
请输入两个数,x和y(x^y):
1 1
1^1的最后三位是:001

$ ./demo
请输入两个数,x和y(x^y):
2 4
2^4的最后三位是:016

$ ./demo
请输入两个数,x和y(x^y):
5 9
5^9的最后三位是:125

案例ex54: 计算某日是该年的第几天

1 题目

功能:计算某日是该年的第几天

描述:计算天数的代码,从键盘输入年、月、日,在屏幕输出此日期是改年的第几天

2 思路

判断年份是闰年还是平年,两点:要么可以直接被 400 整除,要么能被 4 整除,但是不能被 100 整除

if (a % 4 == 0 && a % 100 != 0 || a % 400 == 0)
    return 1;								
else
    return 0;

判断该天是改年的第几天,那么需要知道每个月有多少天。另外,2 月份需要首先判断是平年还是闰年,再来进行后续的计算

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:计算某日是该年的第几天
描述:计算天数的代码,从键盘输入年、月、日,在屏幕输出此日期是改年的第几天
**/

int leap(int a)	{	 									// 判断是否为闰年
    if (a % 4 == 0 && a % 100 != 0 || a % 400 == 0)		// 闰年判定条件
        return 1;										// 是闰年返回1
    else
        return 0;										// 不是闰年返回0
}

int number(int year, int m, int d) {					// 参数 年 月 日,计算是该年的第几天

    int sum = 0, i;
    // 平年每月的天数
    int a[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    // 闰年每月的天数
    int b[12] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

    if (leap(year) == 1)								// 判断是否为闰年
        for (i = 0; i < m - 1; i++)
            sum += b[i];								// 是闰年,累加数组b前m-1个月份天数
    else
        for (i = 0; i < m - 1; i++)
            sum += a[i];								// 不是闰年,累加数组a钱m-1个月份天数
    sum += d;														// 将前面累加的结果加上日期,求出总天数
    return sum;													// 将计算的天数返回
}

int main(int argc, char const *argv[]) {
    int year, month, day, n;										// 定义变量为基本整型
    printf("请输入年月日\n");
    scanf("%d%d%d", &year, &month, &day);				// 输入年月日
    n = number(year, month, day);								// 调用函数number
    printf("%d.%d.%d 是 %d 年的第 %d 天\n", year, month, day, year, n);
}

示例结果:

$ gcc ex054.c -o demo
$ ./demo
请输入年月日
2020 12 31
2020.12.31 是 2020 年的第 366 天

案例ex55: 婚礼上的谎言

1 题目

功能:婚礼上的谎言

描述:

3 对儿情侣参加婚礼,3 个新浪为A、B、C,3 个新娘为 X、Y、Z

有人想知道究竟谁与谁结婚,X 说他的未婚夫为C,C 说他将和 Z 结婚。这人们时候都知道他们在开玩笑,说全是假的。

那么,究竟谁与谁结婚呢?

2 思路

利用穷举法进行计算

然后再利用题目中的谎话进行逻辑判断

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:婚礼上的谎言
描述:
3 对儿情侣参加婚礼,3 个新浪为A、B、C,3 个新娘为 X、Y、Z
有人想知道究竟谁与谁结婚,X 说他的未婚夫为C,C 说他将和 Z 结婚。这人们时候都知道他们在开玩笑,说全是假的。
那么,究竟谁与谁结婚呢?
**/

int main(int argc, char const *argv[]) {
    int a, b, c;
    for (a = 1; a <= 3; a++)								// 穷举a的所有可能
        for (b = 1; b <= 3; b++)							// 穷举b的所有可能
            for (c = 1; c <= 3; c++)						// 穷举c的所有可能
                if (a != 1 && c != 1 && c != 3 && a != b && a != c && b != c) {
												// 如果表达式为真,则输出结果,否则继续下次循环
        printf("%c 将嫁给 A\n", 'X' + a - 1);
        printf("%c 将嫁给 B\n", 'X' + b - 1);
        printf("%c 将嫁给 C\n", 'X' + c - 1);
    }
}

示例结果:

$ gcc ex055.c -o demo
$ ./demo
Z 将嫁给 A
X 将嫁给 B
Y 将嫁给 C

案例ex56: 百元买百鸡

1 题目

功能:百元买百鸡

描述:

中国占代数学家张丘建在他的《算经》中提出了一个著名的“百钱买百鸡闷题”

鸡翁一,值钱五,鸡母一,值钱三,鸡雏三,值钱一,百钱买百鸡,问翁、母、雏各几何?

2 思路

要点

所买的 3 种鸡的钱数总和是 100

所买的 3 种鸡的数量总和是 100

所买的小鸡的数量必须是 3 的倍数

3 代码

# include <stdio.h>
# include <stdlib.h>

/**
功能:百元买百鸡
描述:
中国占代数学家张丘建在他的《算经》中提出了一个著名的“百钱买百鸡闷题”
鸡翁一,值钱五,鸡母一,值钱三,鸡雏三,值钱一,百钱买百鸡,问翁、母、雏各几何?
**/

int main(int argc, char const *argv[]) {
    int cock, hen, chick;										// 定义变量为基本整型
    for (cock = 0; cock <= 20; cock++)								// 公鸡范围在0到20之间
        for (hen = 0; hen <= 33; hen++)							// 母鸡范围在0到33之间
            for (chick = 3; chick <= 99; chick++)						// 小鸡范围在3到99之间
                if (5 *cock + 3 * hen + chick / 3 == 100) 				// 判断钱数是否等于100
                    if (cock + hen + chick == 100) 				// 判断购买的鸡数是否等于100
                        if (chick % 3 == 0) 						// 判断小鸡数是否能被3整除
                            printf("公鸡:%d 母鸡:%d 小鸡:%d\n", cock, hen,chick);
}

示例结果:

$ gcc ex056.c -o demo
$ ./demo
公鸡:0 母鸡:25 小鸡:75
公鸡:4 母鸡:18 小鸡:78
公鸡:8 母鸡:11 小鸡:81
公鸡:12 母鸡:4 小鸡:84

案例ex57: 判断三角形的类型

1 题目

功能:判断三角形的类型

描述:根据给定的三条边判断是否能构成三角形,并且输出它对应的面积和三角形类型

2 思路

从键盘中输入三条边后,只需判断这三条边中任意两边之和是否大于第三边

如果满足条件,可以构成三角形

再做进一步判断确定该三角形是什么三角形

若两边相等-则是等腰三角形;若三边相等,则是等边三角形;若三边满足勾股定理,则是直角三角形

另外,注意 && 和 || 的使用,以及 & 与 && 的区别和 | 与 || 的区别

3 代码

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

/**
功能:判断三角形的类型
描述:根据给定的三条边判断是否能构成三角形,并且输出它对应的面积和三角形类型
**/

int main(int argc, char const *argv[]) {
	float a, b, c;
	float s, area;
	printf("输入三角形的三条边长(以空格分隔):");
	scanf("%f %f %f", &a, &b, &c); // 输入三条边
	printf("a = %f\n", a);
	printf("b = %f\n", b);
	printf("c = %f\n", c);
	if (a + b > c && b + c > a && a + c > b) {					// 判断两边之和是否大于第三边
		s = (a + b + c) / 2;
		area = (float)sqrt(s *(s - a)*(s - b)*(s - c)); 		// 计算三角形的面积
		printf("面积是:%f\n", area); 							// 输出三角形的面积
		if (a == b && a == c)									// 判断三条边是否相等
			printf("三条边组成的三角形是: 等边三角形\n");
			// 输出等边三角形
		else if (a == b || a == c || b == c)
			// 判断三角形中是否有两边相等
			printf("三条边组成的三角形是: 等腰三角形\n");
			// 输出等腰三角形
		else if ((a * a + b * b == c * c) || (a * a + c * c == b * b) || (b * b + c * c == a *a))
			// 判断是否有两边的平方和大于第三边的平方
			printf("三条边组成的三角形是: 直角三角形\n");
			// 输出直角三角形
		else
			printf("三条边组成的三角形是: 普通三角形");
			// 普通三角形
	}
	else
		// 如果两边之和小于第三边不能组成三角形
		printf("该三条边不能构成三角形\n");
}

示例结果:

$ gcc ex057.c -o demo
$ ./demo
输入三角形的三条边长(以空格分隔):3 4 5
a = 3.000000
b = 4.000000
c = 5.000000
面积是:6.000000
三条边组成的三角形是: 直角三角形

案例ex58: 直接插入排序

1 题目

功能:直接插入排序

描述:利用直接插入排序进行将数组序列从小到大排序

2 思路

原始顺序: 34, 12, 45, 3, 8, 23, 89, 52, 24, 10

在代码中将数组 a[0] 置为监视哨

趟数 监视哨 排序结果
1 34 (12,) 34, 45, 3, 8, 23, 89, 52, 24, 10
2 12 (12, 34,) 45, 3, 8, 23, 89, 52, 24, 10
3 45 (12, 34, 45,) 3, 8, 23, 89, 52, 24, 10
4 3 (3, 12, 34, 45,) 8, 23, 89, 52, 24, 10
5 8 (3, 8, 12, 34, 45,) 23, 89, 52, 24, 10
6 23 (3, 8, 12, 23, 34, 45,) 89, 52, 24, 10
7 89 (3, 8, 12, 23, 34, 45, 89,) 52, 24, 10
8 52 (3, 8, 12, 23, 34, 45, 52, 89,) 24, 10
9 24 (3, 8, 12, 23, 24, 34, 45, 52, 89,) 10
10 10 (3, 8, 10, 12, 23, 24, 34, 45, 52, 89,)

以上是整个的插入排序算法的过程

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:直接插入排序
描述:利用直接插入排序进行将数组序列从小到大排序
**/

void insort(int s[], int n)	{					// 自定义函数isort

    int i, j;
    for (i = 2; i <= n; i++) {					// 数组下标从2开始,0 位置做监视哨,1位置一个数据无可比性
        s[0] = s[i];							// 给监视哨赋值
        j = i - 1;								// 确定要进行比较的元素的最右边位置
        while (s[0] < s[j]) {
            s[j + 1] = s[j];					// 数据右移
            j--;								// 移向左边一个未比较的数
        }
        s[j + 1] = s[0];						// 在确定的位置插入s[i]
    }
}

int main(int argc, char const *argv[]) {
    int a[11], i;									// 定义数组及变量为基本整型
    printf("请输入10个数据:\n");
    for (i = 1; i <= 10; i++)
        scanf("%d", &a[i]);							// 接收从键盘中输入的10个数据到数组a中
    printf("原始顺序:\n");
    for (i = 1; i < 11; i++)
        printf("%3d", a[i]);						// 将未排序前的顺序输出
    insort(a, 10);									// 调用自定义函数isort()
    printf("\n插入数据排序后顺序:\n");
    for (i = 1; i < 11; i++)
        printf("%3d", a[i]);						// 将排序后的数组输出
    printf("\n");
}

示例结果:

$ gcc ex058.c -o demo
$ ./demo
请输入10个数据:
34
12
45
3
8
23
89
52
24
10
原始顺序:
 34 12 45  3  8 23 89 52 24 10
插入数据排序后顺序:
  3  8 10 12 23 24 34 45 52 89

案例ex59: 希尔排序

1 题目

功能:希尔排序

描述:利用希尔排序进行将数组序列从小到大排序

2 思路

希尔排序是在直接插入排序的基础上做的改进,将要排序的序列按固定增量分成若干组,等距离者在同一组中,然后再程纽内进行直接插入排序。

这里面的固定增量从n/2开始,以后每次缩小到原来的一半.

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:希尔排序
描述:利用希尔排序进行将数组序列从小到大排序
**/

void shsort(int s[], int n)	{
    int i, j, d;
    d = n / 2;													// 确定固定增量值
    while (d >= 1){
        for (i = d + 1; i <= n; i++) {							// 数组下标从d+1开始进行直接插入排序
            s[0] = s[i];										// 设置监视哨
            j = i - d;											// 确定要进行比较的元素的最右边位置
            while ((j > 0) && (s[0] < s[j])) {
                s[j + d] = s[j];								// 数据右移
                j = j - d;										// 向左移d个位置
            }
            s[j + d] = s[0];									// 在确定的位置插入s[i]
        }
        d = d / 2;												// 增量变为原来的一半
    }
}

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

    int a[11], i;												// 定义数组及变量为基本整型
    printf("请输入10个数据:\n");
    for (i = 1; i <= 10; i++)
        scanf("%d", &a[i]);										// 从键盘中输入10个数据
    shsort(a, 10);												// 调用shsort()函数
    printf("排序后的顺序是:\n");
    for (i = 1; i <= 10; i++)
        printf("%5d", a[i]);									// 将排好序的数组输出
	printf("\n");
}

示例结果:

$ gcc ex059.c -o demo
$ ./demo
请输入10个数据:
34
15
56
12
90
43
9
7
93
100
排序后的顺序是:
    7    9   12   15   34   43   56   90   93  100

案例ex60: 冒泡排序

1 题目

功能:冒泡排序

描述:利用冒泡排序进行将数组序列从小到大排序

2 思路

冒泡法的基本思路是

如果要对 n 个数进行冒泡排序,那么要进行 n-1 趟比较,在第1趟比较中要进行 n-1 次两两比较,在第 j 趟比较中要进行 n-j 次两两比较

从这个基本思路中就会发现,趟数决定了两两比较的次数,这样就很容易将两个 for 循环联系起来了

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:冒泡排序
描述:利用冒泡排序进行将数组序列从小到大排序
**/

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

    int i, j, t, a[11];							// 定义变量及数组为基本整型
    printf("请输入10个数:\n");
    for (i = 1; i < 11; i++)
        scanf("%d", &a[i]);						// 从键盘中输入10个数
    for (i = 1; i < 10; i++)					// 变量i代表比较的趟数
        for (j = 1; j < 11-i; j++)				// 变量j代表每趟两两比较的次数
    if (a[j] > a[j + 1]) {
        t = a[j];								// 利用中间变量实现俩值互换
        a[j] = a[j + 1];	
        a[j + 1] = t;
    }
    printf("排序后的顺序是:\n");
    for (i = 1; i <= 10; i++)
        printf("%5d", a[i]);					// 将冒泡排序后的顺序输出
	printf("\n");
}

示例结果:

$ gcc ex060.c -o demo
$ ./demo
请输入10个数:
56
34
16
87
134
14
1
84
55
90
排序后的顺序是:
    1   14   16   34   55   56   84   87   90  134

案例ex61: 快速排序

1 题目

功能:快速排序

描述:利用快速排序进行将数组序列从小到大排序

2 思路

在待排序的n个数据中取第 1 个数据作为基准值,将所有记录分为 3 组

使笫一组中各数据值均小于或等于基准值,第二组做基准值的数据,第三组中各数据值均人于或等于基准值,这便实现了第一趟分割,然后再对第一组和第三组分别重复上述方法。

依次类推;直到每组中只有一个记录为止

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:快速排序
描述:利用快速排序进行将数组序列从小到大排序
**/

void qusort(int s[], int start, int end) {					// 自定义函数qusort()

    int i, j;												// 定义变量为基本整型
    i = start;												// 将每组首个元素赋给i
    j = end;												// 将每组末尾元素赋给j
    s[0] = s[start];										// 设置基准值
    while (i < j){
        while (i < j && s[0] < s[j])
            j--;											// 位置左移
        if (i < j){
            s[i] = s[j];									// 将s[j]放到s[i]的位置上
            i++;											// 位置右移
        }
        while (i < j && s[i] <= s[0])
            i++;											// 位置右移
        if (i < j){
            s[j] = s[i];									// 将大于基准值的s[j]放到s[i]位置
            j--;											// 位置右移
        }
    }
    s[i] = s[0];												// 将基准值放入指定位置
    if (start < i)
        qusort(s, start, j - 1);								// 对分割出的部分递归调用函数qusort()
    if (i < end)
        qusort(s, j + 1, end);
}

int main(int argc, char const *argv[]) {
    int a[11], i;												// 定义数组及变量为基本整型
    printf("请输入10个数:\n");
    for (i = 1; i <= 10; i++)
        scanf("%d", &a[i]);										// 从键盘中输入10个要进行排序的数
    qusort(a, 1, 10);											// 调用qusort()函数进行排序
    printf("排序后的顺序是:\n");
    for (i = 1; i <= 10; i++)
        printf("%5d", a[i]);										// 输出排好序的数组
	printf("\n");
}

示例结果:

$ gcc ex061.c -o demo
$ ./demo
请输入10个数:
45
12
76
2
45
16
34
61
24
90
排序后的顺序是:
    2   12   16   24   34   45   45   61   76   90

案例ex62: 选择排序

1 题目

功能:选择排序

描述:利用选择排序进行将数组序列从小到大排序

2 思路

选择排序的基本算法是从待排序的区间中经过选择和交换后选出最小的数值存放到a[0]中,再从剩余的未排序区间中经过选择和交换后选出最小的数值存放到a[1]中,a[1]中的数字仅大于a[0],依此类推,即可实现排序

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:选择排序
描述:利用选择排序进行将数组序列从小到大排序
**/

int main(int argc, char const *argv[]) {
    int i, j, t, a[11];						//定义变量及数组为基本整型
    printf("请输入10个数:\n");
    for (i = 1; i < 11; i++)
        scanf("%d", &a[i]);					//从键盘中输入要排序的10个数字
    for (i = 1; i <= 9; i++)
        for (j = i + 1; j <= 10; j++)
            if (a[i] > a[j]) {			//如果后一个数比前一个数大则利用中间变量t实现俩值互换
		        t = a[i];
		        a[i] = a[j];
		        a[j] = t;
    }
    printf("排序后的顺序是:\n");
    for (i = 1; i <= 10; i++)
        printf("%5d", a[i]);				//将排好序的数组输出
	    printf("\n");
}

示例结果:

$ gcc ex062.c -o demo
$ ./demo
请输入10个数:
23
51
34
15
45
72
2
5
33
90
排序后的顺序是:
    2    5   15   23   33   34   45   51   72   90

案例ex63: 归并排序

1 题目

功能:归并排序

描述:利用归并排序进行将数组序列从小到大排序

2 思路

归并排序两个或多个有序记录序列合并成一个有序序列

一次对两个有序记录序的归并称为二路归并排序,也有三路归并排序及多路归并排序

下面代码给出的是二路归并排序,基本方法

(1)将n个记录看成是n介长度为1的有序子表

(2)将两两相邻的有序壬莓4行归并

(3)垂复轨行步骤(2)

直至归并成一个长度为 L 的有序表

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:归并排序
描述:利用归并排序进行将数组序列从小到大排序
**/

void merge(int r[], int s[], int x1, int x2, int x3) { // 实现一次归并排序函数
    int i, j, k;
    i = x1; 										// 第一部分的开始位置
    j = x2 + 1; 										// 第二部分的开始位置
    k = x1;
    while ((i <= x2) && (j <= x3))				// 当i和j都在两个要合并的部分中
        if (r[i] <= r[j]){						// 筛选两部分中较小的元素放到数组s中
        s[k] = r[i];
        i++;
        k++;
    } else {
        s[k] = r[j];
        j++;
        k++;
    }
    while (i <= x2)								// 将x1~x2范围内的未比较的数顺次加到数组r中
        s[k++] = r[i++];
    while (j <= x3)								// 将x2+1~x3范围内的未比较的数顺次加到数组r中
        s[k++] = r[j++];
}

void merge_sort(int r[], int s[], int m, int n) {
    int p;
    int t[20];
    if (m == n)
        s[m] = r[m];
    else
    {
        p = (m + n) / 2;
        merge_sort(r, t, m, p);
            									// 递归调用merge_sort函数将r[m]~r[p]归并成有序的t[m]~t[p]
        merge_sort(r, t, p + 1, n);				// 递归调用merge_sort函数将r[n+1]~r[n]归并成有序的t[p+1]~t[n]*/
        merge(t, s, m, p, n); 					// 调用函数将前两部分归并到s[m]~s[n]
    }
}

int main(int argc, char const *argv[]) {
    int a[11];
    int i;
    printf("请输入10个数:\n");
    for (i = 1; i <= 10; i++)
        scanf("%d", &a[i]);
    merge_sort(a, a, 1, 10);					 // 调用merge_sort函数进行归并排序
    printf("排序后的顺序是:\n");
    for (i = 1; i <= 10; i++)
        printf("%5d", a[i]);
	    printf("\n");
}

示例结果:

$ gcc ex063.c -o demo
$ ./demo
请输入10个数:
34
12
64
23
98
45
18
52
1
7
排序后的顺序是:
    1    7   12   18   23   34   45   52   64   98

案例ex64: 二分查找

1 题目

功能:二分查找

描述:

使用二分查找特定关键字元素

用户输入有序数组的关键字后,给定要查找的关键字,看是否存在于有序数组中

2 思路

二分査找就是折半查找

其基本思想:首先选取表中间位置的记录,将其关键字与给定关键字key进行比较,若相等,则査找成功;

若key值比该关键字值大,则要找的元素一定在右子表中,则继续对右子表进行折半査找;

若key值比该关键字值小,则要找的元素一定在左子表中,继续对左子表进行折半査找;

按照上述递推,直到查找成功或查找失败。

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:二分查找
描述:
	使用二分查找特定关键字元素
	用户输入有序数组的关键字后,给定要查找的关键字,看是否存在于有序数组中
**/

void binary_search(int key, int a[], int n) { // 自定义函数binary_search
    int low, high, mid, count = 0, count1 = 0;
    low = 0;
    high = n - 1;
    while (low < high) {							// 当查找范围不为0时执行循环体语句

        count++;								// count记录查找次数
        mid = (low + high) / 2;						// 求出中间位置
        if (key < a[mid])							// 当key小于中间值
            high = mid - 1;						// 确定左子表范围
        else if (key > a[mid])						// 当key大于中间值
            low = mid + 1;							// 确定右子表范围
        else if (key == a[mid]) {					// 当key等于中间值证明查找成功

            printf("查找成功!\n查找 %d 次!a[%d]=%d", count, mid, key);
											// 输出查找次数及所查找元素在数组中的位置
            count1++;							// count1记录查找成功次数
            break;
        }
    }
    if (count1 == 0)								// 判断是否查找失败
        printf("查找失败!");							// 查找失败输出no found
}

int main(int argc, char const *argv[]) {
    int i, key, a[100], n;
    printf("请输入数组的长度:\n");
    scanf("%d", &n);								// 输入数组元素个数
    printf("请输入数组元素:\n");
    for (i = 0; i < n; i++)
        scanf("%d", &a[i]);							// 输入有序数列到数组a中
    printf("请输入你想查找的元素:\n");
    scanf("%d", &key);								// 输入要查找的关键字
    binary_search(key, a, n);						// 调用自定义函数
	printf("\n");
}

示例结果:

$ gcc ex064.c -o demo
$ ./demo
请输入数组的长度:
10
请输入数组元素:
1
4
6
9
12
46
78
90
102
122
请输入你想查找的元素:
102
查找成功!
查找 3 次!a[8]=102

案例ex65: 分块查找

1 题目

功能:分块查找

描述:利用分块查找的思想,将指定的数据项查找出来

2 思路

分块查找是折半查找和顺序查找的一种改进方法, 分块查找由于只要求索引表是有序的, 对块内节点没有排序要求, 因此特别适合于节点动态变化的情况。

要求将待査的元素均匀地分成块, 块间按大小排序, 块内不排序, 所以要建立一个块的最大(或最小)关键字表, 称为索引表。

本例子中将给出的15个数按关键字大小分成了3块, 这15个数的排列是一个有序序列, 也可以给出无序序列, 但必须满足分在第一块中的任意数都小于第二块中的所有数, 第二块中的所有数都小于第三块中的所有数。

当要査找关键字为key的元素时, 先用顺序查找在已建好的索引表中査出key所在的块中, 再在对应的块中顺序查找key, 若key存在, 则输出其相应位置, 否则输出提示信息。

3 代码

#include <stdio.h>
#include <stdlib.h>


/**
功能:分块查找
描述:利用分块查找的思想,将指定的数据项查找出来
**/

struct index {											// 定义块的结构
    int key;												// 存放块内最大值
    int start;											// 存放块内起始值
    int end;												// 存放块内结束值
} index_table[4]; 									// 结构体数组

int block_search(int key, int a[]) { 					// 自定义分块查找

    int i, j;
    i = 1;
    while (i <= 3 && key > index_table[i].key)			// 确定在那个块中
        i++;
    if (i > 3)																// 大于分得的块数, 则返回0
        return 0;
    j = index_table[i].start; 								// j等于块范围的起始值
    while (j <= index_table[i].end && a[j] != key)		// 在确定的块内进行查找
        j++;
    if (j > index_table[i].end)									// 如果大于块范围的结束值, 则说明没有要查找的数,j置0
        j = 0;
    return j;
}

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

    int i, j = 0, k, key, a[16];
    printf("请输入15个数:\n");
    // 输入由小到大的15个数,由于是测试数据,那么必须要保证在块之间是整体有序的,块内无序
    for (i = 1; i < 16; i++)
        scanf("%d", &a[i]);								
    for (i = 1; i <= 3; i++) {
        index_table[i].start = j + 1; 					// 确定每个块范围的起始值
        j = j + 1;
        index_table[i].end = j + 4; 					// 确定每个块范围的结束值
        j = j + 4;
        index_table[i].key = a[j]; 						// 确定每个块范围中元素的最大值
    }
    printf("请输入你想查找的元素:\n");
    scanf("%d", &key); 									// 输入要查询的数值
    k = block_search(key, a); 							// 调用函数进行查找
    if (k != 0)	
        printf("查找成功, 其位置是:%d\n", k);				// 如果找到该数, 则输出其位置
    else
        printf("查找失败!");								// 若未找到则输出提示信息
}

示例结果:

$ gcc ex065.c -o demo
$ ./demo
请输入15个数:
1
4
6
8
10
14
16
19
21
25
29
30
31
34
40
请输入你想查找的元素:
29
查找成功, 其位置是:11

案例ex66: 哈希查找

1 题目

功能:哈希查找

描述:

哈希表长度 11

哈希函数 (key)=key%11

采用线性探测再散列的方法处理冲突

2 思路

哈希函数简要介绍

哈希函数的构造方法

哈希函数的构造方法常用的有5种,分别是数字分析法、平方取中法、分段叠加、伪随机数法和余数法,其中余数法比较常用。

例子中已给出哈希函数,按照给出的哈希函数进行了构造

避免哈希冲突的方法

分别有开放定址法(包括线性探测再散列和二次探测再散列入、链地址法、再哈希法和建立公共溢出区

开放定址法中的线性探测再散列比较常用,该方法的特点是在冲突发生时,顺序查看表中的下一单元,直到找出一个空单元或査遍全表。

3 代码

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define Max 11
#define N 8

/**
功能:哈希查找
描述:
	哈希表长度 11
	哈希函数 (key)=key%11
	采用线性探测再散列的方法处理冲突
**/

int hashtable[Max];

int func(int value) {
    return value % Max; 							// 哈希函数
}

int search(int key) { 								// 自定义函数实现哈希查询

    int pos, t;
    pos = func(key); 								// 哈希函数确定出的位置
    t = pos;										// t存放确定出的位置
    while (hashtable[t] != key && hashtable[t] !=  - 1) {
		// 如果该位置上不等于要查找的关键字且不为空
        t = (t + 1) % Max; 							// 利用线性探测求出下一个位置
        if (pos == t)
			// 如果经多次探测又回到原来用哈希函数求出的位置则说明要查找的数不存在
            return  - 1;
    }
    if (hashtable[t] ==  - 1)						// 如果探测的位置是-1则说明要查找的数不存在
        return -1;
    else
        return t;
}

void creathash(int key) {							// 自定义函数创建哈希表

    int pos, t;
    pos = func(key);								// 哈希函数确定元素的位置
    t = pos;
    while (hashtable[t] !=  - 1) {
		// 如果该位置有元素存在则进行线性探测再散列
        t = (t + 1) % Max;
        if (pos == t) {
        	// 如果冲突处理后确定的位置与原位置相同则说明哈希表已满
            printf("哈希表已满\n");
            return ;
        }
    }
    hashtable[t] = key;								// 将元素放入确定的位置
}

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

    int flag[50];
    int i, j, t;
    for (i = 0; i < Max; i++)
        hashtable[i] =  - 1;				// 哈希表中初始位置全置-1
     										
    for (i = 0; i < 50; i++)
        flag[i] = 0;
     										// 50以内所有数未产生时均标志为0
    srand((unsigned long)time(0)); 			// 利用系统时间做种子产生随机数
    i = 0;
    printf("建立 Hash 表: \n");
    while (i != N) {
        t = rand() % 50; 					// 产生一个50以内的随机数赋给t
        if (flag[t] == 0) {						// 查看t是否产生过
            creathash(t);						// 调用函数创建哈希表
            printf("%2d:", t); 					// 将该元素输出
            for (j = 0; j < Max; j++)
                printf("(%2d) ", hashtable[j]);
             									// 输出哈希表中内容
            printf("\n");
            flag[t] = 1; 						// 将产生的这个数标志为1
            i++;								// i自加
        }
    }
    printf("请输入你想查找的元素:");
    scanf("%d", &t); 							// 输入要查找的元素
    if (t > 0 && t < 50) {
        i = search(t);							// 调用search进行哈希查找
        if (i !=  - 1)
            printf("查找成功!其位置是:%d\n", i);	// 若查找到该元素则输出其位置
        else
            printf("查找失败!");					// 未找到输出提示信息
    }
    else
        printf("输入有误!");
}

示例结果:

$ gcc ex066.c -o demo
$ ./demo
建立 Hash 表:
 7:(-1) (-1) (-1) (-1) (-1) (-1) (-1) ( 7) (-1) (-1) (-1)
22:(22) (-1) (-1) (-1) (-1) (-1) (-1) ( 7) (-1) (-1) (-1)
16:(22) (-1) (-1) (-1) (-1) (16) (-1) ( 7) (-1) (-1) (-1)
29:(22) (-1) (-1) (-1) (-1) (16) (-1) ( 7) (29) (-1) (-1)
44:(22) (44) (-1) (-1) (-1) (16) (-1) ( 7) (29) (-1) (-1)
37:(22) (44) (-1) (-1) (37) (16) (-1) ( 7) (29) (-1) (-1)
 3:(22) (44) (-1) ( 3) (37) (16) (-1) ( 7) (29) (-1) (-1)
 9:(22) (44) (-1) ( 3) (37) (16) (-1) ( 7) (29) ( 9) (-1)
请输入你想查找的元素:9
查找成功!其位置是:9

案例ex67: 斐波那契数列

1 题目

功能:斐波那契数列

描述:实现一个斐波那契数列,并且打印出来

2 思路

斐波那契数列(Fibonacci sequence),又称黄金分割数列

斐波那契数列以如下被以递推的方法定义:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)

即这样的一个序列:这样一个数列:0、1、1、2、3、5、8、13、21、34

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:斐波那契数列
描述:实现一个斐波那契数列,并且打印出来
**/

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

    int i;												// 定义整型变量i
    long f[51];											// 意义数组为长整形
    f[1] = 1, f[2] = 1;									// 数组中的f[1]、f[2]赋初值为1
    for (i = 3; i < 51; i++)
        f[i] = f[i - 1] + f[i - 2];						// 数列中从第3项开始每一项等于前两项之和
    for (i = 1; i < 51; i++) {

        printf("%12ld", f[i]);							// 输出数组中的 30 个元素
        if (i % 5 == 0)
            printf("\n");								// 每 5 个元素进行一次换行
    }
}

示例结果:

$ gcc ex067.c -o demo
$ ./demo
           1           1           2           3           5
           8          13          21          34          55
          89         144         233         377         610
         987        1597        2584        4181        6765
       10946       17711       28657       46368       75025
      121393      196418      317811      514229      832040
     1346269     2178309     3524578     5702887     9227465
    14930352    24157817    39088169    63245986   102334155
   165580141   267914296   433494437   701408733  1134903170
  1836311903  2971215073  4807526976  7778742049 12586269025

案例ex68: 哥德巴赫猜想

1 题目

功能:哥德巴赫猜想

描述:

任一大于2的整数都可写成两个质数之和

验证从 3 到 50 之间所有数都是否可以写成两个素数之和

2 思路

  1. 创建判断是否为素数的函数
  2. 从 3 到 50 循环判断是否可以有 2 个素数加和

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:哥德巴赫猜想
描述:
	任一大于2的整数都可写成两个质数之和
	验证从 3 到 50 之间所有数都是否可以写成两个素数之和
**/

int ss(int i) {	 												// 素数的判断

    int j;
    if (i <= 1) 												// 小于1的数不是素数
        return 0;
    if (i == 2) 												// 2是素数
        return 1;
    for (j = 2; j < i; j++)	{						// 对大于2的数进行判断
        if (i % j == 0)
            return 0;
        else if (i != j + 1)
            continue;
        else
            return 1;
    }
}

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

    int i, j, k, flag1, flag2, n = 0;
    for (i = 3; i <= 50; i++)
	    for (k = 2; k <= i / 2; k++) {
	        j = i - k;
	        flag1 = ss(k);										// 判断拆分出的数是否是素数
	        if (flag1) {
	            flag2 = ss(j);
	            if (flag2) {	 								// 如果拆分出的两个数均是素数则输出
	                printf("%d = %d+%d,\t", i, k, j);
	                n++;
	                if (n % 5 == 0)
	                    printf("\n");
	            }
	        }
	    }
	printf("\n");
}

示例结果:

$ gcc ex068.c -o demo
$ ./demo
4 = 2+2,	5 = 2+3,	6 = 3+3,	7 = 2+5,	8 = 3+5,
9 = 2+7,	10 = 3+7,	10 = 5+5,	12 = 5+7,	13 = 2+11,
14 = 3+11,	14 = 7+7,	15 = 2+13,	16 = 3+13,	16 = 5+11,
18 = 5+13,	18 = 7+11,	19 = 2+17,	20 = 3+17,	20 = 7+13,
21 = 2+19,	22 = 3+19,	22 = 5+17,	22 = 11+11,	24 = 5+19,
24 = 7+17,	24 = 11+13,	25 = 2+23,	26 = 3+23,	26 = 7+19,
26 = 13+13,	28 = 5+23,	28 = 11+17,	30 = 7+23,	30 = 11+19,
30 = 13+17,	31 = 2+29,	32 = 3+29,	32 = 13+19,	33 = 2+31,
34 = 3+31,	34 = 5+29,	34 = 11+23,	34 = 17+17,	36 = 5+31,
36 = 7+29,	36 = 13+23,	36 = 17+19,	38 = 7+31,	38 = 19+19,
39 = 2+37,	40 = 3+37,	40 = 11+29,	40 = 17+23,	42 = 5+37,
42 = 11+31,	42 = 13+29,	42 = 19+23,	43 = 2+41,	44 = 3+41,
44 = 7+37,	44 = 13+31,	45 = 2+43,	46 = 3+43,	46 = 5+41,
46 = 17+29,	46 = 23+23,	48 = 5+43,	48 = 7+41,	48 = 11+37,
48 = 17+31,	48 = 19+29,	49 = 2+47,	50 = 3+47,	50 = 7+43,
50 = 13+37,	50 = 19+31,

案例ex69: 尼科彻斯定理

1 题目

功能:尼科彻斯定理

描述:

验证尼科彻斯定理。即:任何一个整数 m 的立方都可以写成 m 个连续奇数之和。m属于[1,100],超出范围则报错。

例如:

1^3=1

2^3=3+5

3^3=7+9+11

4^3=13+15+17+19

2 思路

循环判断实现尼科彻斯定理

3 代码

#include <stdio.h>
#include <stdlib.h>


/**
功能:尼科彻斯定理
描述:
	验证尼科彻斯定理。即:任何一个整数 m 的立方都可以写成 m 个连续奇数之和。m属于[1,100],超出范围则报错。
	例如:
	1^3=1
	2^3=3+5
	3^3=7+9+11
	4^3=13+15+17+19
**/


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

    int i, j, k = 0;
    int l, n, m, sum, flag=1;
    printf("请输入一个数:\n");
    scanf("%d", &n);									// 从键盘中任意输入一个数
    m = n * n * n;										// 计算出该数的立方
    i = m / 2;
    if (i % 2 == 0)										// 当i为偶数时i值加1
        i = i + 1;
    while (flag == 1 && i >= 1) {						// 当i大于等于1且flag=1时执行循环体语句
        sum = 0;
        k = 0;
        while (1) {
            sum += (i - 2 * k);							// 奇数累加求和
            k++;
            if (sum == m) {									// 如果sum与m相等,则输出累加过程

                printf("%d*%d*%d=%d=", n, n, n, m);
                for (l = 0; l < k - 1; l++)
                    printf("%d+", i - l * 2);
                printf("%d\n", i - (k - 1) *2);			 // 输出累加求和的最后一个数
                flag=0;
                break;
            }
            if (sum > m)
                break;
        }
        i -= 2;											// i等于下一个奇数继续上面过程
    }
}

示例结果:

$ gcc ex069.c -o demo
$ ./demo
请输入一个数:
12
12*12*12=1728=865+863

案例ex70: 创建单向链表

1 题目

功能:创建单向链表

描述:创建Node结构体,构造一个单链表

2 思路

  1. 创建结点的结构体,分布由结点值和指针组成
  2. 循环创建每个结点,使得上一个结点指向下一个结点

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:创建单向链表
描述:创建Node结构体,构造一个单链表
**/

struct LNode {
    int data;
    struct LNode *next;
};

struct LNode *create_listnode(int n) {

    int i;
    struct LNode *head, *p1, *p2;
    int a;
    head = NULL;
    printf("输入结点内容:\n");
    for (i = n; i > 0; i--) {
        p1 = (struct LNode*)malloc(sizeof(struct LNode)); // 分配空间
        scanf("%d", &a); // 输入数据
        p1->data = a; // 数据域赋值
        if (head == NULL) { // 指定头结点
            head = p1;
            p2 = p1;
        }
        else {
            p2->next = p1; // 指定后继指针
            p2 = p1;
        }
    }
    p2->next = NULL;
    return head;
}

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

    int n;
    struct LNode *lnode;
    printf("输入要创建单链表的结点个数:");
    scanf("%d", &n); // 输入链表结点个数
    lnode = create_listnode(n);
    printf("结果是:\nhead->");
    while (lnode) {
        printf("%d -> ", lnode->data); // 输出链表
        lnode = lnode->next;
    };
	printf("\n");
}

示例结果:

$ gcc ex070.c -o demo
$ ./demo
输入要创建单链表的结点个数:5
输入结点内容:
1
2
3
4
5
结果是:
head->1 -> 2 -> 3 -> 4 -> 5 ->

案例ex71: 创建双向链表

1 题目

功能:创建双向链表

描述:

a. 创建一个双链表,实现输入链表中的数据,并且可以将链表中的数据进行输出

b. 输入一个结点内容,把这个结点从链表中删除,随后输出链表内容

2 思路

单链表,存储形式只有一个指向直接后继的指针域,所以只能是从头结点顺着指针域指向的下一个结点进行不断的查找

双向链表,一个结点既可以指向前驱也可以指向后继,对于数据查找来说更加的便捷

3 代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/**
功能:创建双向链表
描述:
	a. 创建一个双链表,实现输入链表中的数据,并且可以将链表中的数据进行输出
	b. 输入一个结点内容,把这个结点从链表中删除,随后输出链表内容
**/

typedef struct node {
    char name[20];
    struct node *prior,  *next;
} stud; 								// 双链表的结构定义

stud *creat(int n) {
    stud *p,  *h,  *s;
    int i;
    h = (stud*)malloc(sizeof(stud));	// 申请结点空间
    h->name[0] = '\0';
    h->prior = NULL;
    h->next = NULL;
    p = h;
    for (i = 0; i < n; i++) {
        s = (stud*)malloc(sizeof(stud));
        p->next = s;					// 指定后继结点
        printf("输入第%d个学生的姓名: ", i + 1);
        scanf("%s", s->name);
        s->prior = p;					// 指定前驱结点
        s->next = NULL;
        p = s;
    }
    p->next = NULL;
    return (h);
}

// 查找
stud *search(stud *h, char *x) {
    stud *p;							// 指向结构体类型的指针
    char *y;
    p = h->next;		
    while (p) {
        y = p->name;
        if (strcmp(y, x) == 0)			// 如果是要删除的节点,则返回地址
            return (p);
        else
            p = p->next;
    }
    printf("没有找到数据!\n");
    return NULL;
}

// 删除
void del(stud *p) {
    p->next->prior = p->prior;			// p的下一个结点的前驱指针指向p的前驱结点
    p->prior->next = p->next;			// p的前驱结点的后继指针指向p的后继结点
    free(p);
}

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

    int number;
    char sname[20];
    stud *head,  *sp;
    puts("请输入链表的大小:");
    scanf("%d", &number);				// 输入链表结点数
    head = creat(number);				// 创建链表
    sp = head->next;
    printf("\n现在这个双链表是:\n");
    while (sp) {						// 输出链表中数据
        printf("%s ", &*(sp->name));
        sp = sp->next;
    }
    printf("\n请输入你想查找的姓名:\n");
    scanf("%s", sname);
    sp = search(head, sname);			// 查找指定结点
    printf("你想查找的姓名是:%s\n",  * &sp->name);
    del(sp);							// 删除结点
    sp = head->next;
    printf("\n现在这个双链表是:\n");
    while (sp) {
        printf("%s ", &*(sp->name));	// 输出当前链表中数据
        sp = sp->next;
    }
    printf("\n");
}

示例结果:

$ gcc ex071.c -o demo
$ ./demo
请输入链表的大小:
5
输入第1个学生的姓名: a
输入第2个学生的姓名: b
输入第3个学生的姓名: c
输入第4个学生的姓名: d
输入第5个学生的姓名: e

现在这个双链表是:
a b c d e
请输入你想查找的姓名:
d
你想查找的姓名是:d

现在这个双链表是:
a b c e

 按任意键退出...

案例ex72: 创建一个单循环链表

1 题目

功能:创建一个单循环链表

描述:输入链表中各个结点中的值,然后进行进行整合成单循环链表进行输出

2 思路

一个循环链表是从简单的链表中发展而来,简单链表带来的弊端是,必须要保存头结点才能不断的遍历链表,否则当遍历到

结点尾部的时候,便没办法进行后续的工作。

简单循环链表带来的优势是,当遍历到链表尾部的时候,由于尾部的next指针指向头部,所以是要比简单的单链表灵活一些的

3 代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/**
功能:创建一个单循环链表
描述:输入链表中各个结点中的值,然后进行进行整合成单循环链表进行输出
**/

typedef struct Node {
    char num;
    struct Node *next;
} LNode, *LinkList;

LinkList create(void) {

    LinkList head;
    LNode *p1,  *p2;
    char a;
    head = NULL;
    a = getchar();
    while (a != '#') {
        p1 = (LNode*)malloc(sizeof(LNode)); 	// 分配空间
        p1->num = a; 				// 数据域赋值
        if (head == NULL)
            head = p1;
        else
            p2->next = p1;
        p2 = p1;
        a = getchar();
    }
    p2->next = head; 				// 尾节点指向头结点
    return head;
}

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

    LinkList L1, head;
    printf("请输入循环链表:(以 # 结束)\n");
    L1 = create();					// 创建循环链表
    head = L1;
    printf("形成的循环链表是:\n");
    printf("%c -> ", L1->num);
    L1 = L1->next;					// 指向下一个结点
    while (L1 != head) { 			// 判断条件为循环到头结点结束

        printf("%c -> ", L1->num);
        L1 = L1->next;
    }
	printf("\n");
}

示例结果:

$ gcc ex072.c -o demo
$ ./demo
请输入循环链表:(以 # 结束)
apple#
形成的循环链表是:
a -> p -> p -> l -> e ->

案例ex73: 头插入法建立单链表

1 题目

功能:头插入法建立单链表

描述:输入链表中各个结点中的值,然后利用头插入法整合成单循环链表进行输出

2 思路

头插法的基本思路是:

a. 首先创将一个空的单链表

b. 生成新的结点插入到头部,然后一直循环直到所有的元素结点都插入到链表中

最后,由于是循环向头部插入,那么导致的一个现象就是,从头部开始遍历的时候,一定与形成时元素结点相反

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:头插入法建立单链表
描述:输入链表中各个结点中的值,然后利用头插入法整合成单循环链表进行输出
**/

typedef struct Node {
    char num;
    struct Node *next;
} LNode,  *LinkList;

LinkList create(void) {
    LinkList head;
    LNode *p1;
    char a;
    head = NULL;
    printf("请输入链表元素(字符采集,不需要分隔符):\n");
    a = getchar();
    while (a != '\n') {
        p1 = (LinkList)malloc(sizeof(LNode)); 		// 分配空间
        p1->num = a; 								// 数据域赋值
        p1->next = head;
        head = p1;
        a = getchar();
    }
    return head;									// 返回头结点
}

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

    LinkList L1;
    L1 = create();
    printf("这个链表是:\n");
    while (L1) {
        printf("%c ", L1->num);
        L1 = L1->next;
    }
	printf("\n");
}

示例结果:

$ gcc ex070.c -o demo
$ ./demo
请输入链表元素(字符采集,不需要分隔符):
apple
这个链表是:
e l p p a

案例ex74: 约瑟夫环

1 题目

功能:约瑟夫环

描述:使用循环链表实现约瑟夫环,给定一组结点数据 {1,2,3,4,5,6,7,8,9,10},然后从报数开始,进行打印

2 科普-什么是约瑟夫环

**约瑟夫环 **

百度百科 - 约瑟夫环

约瑟夫问题是个有名的问题:N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。例如N=6,M=5,被杀掉的顺序是:5,4,6,2,3。

具体例子:

比如说有一个圈中的数字为 {1, 2, 3, 4, 5, 6, 7, 8, 9, 10},第 3 个被杀掉,即从头开始数到 3 的被杀掉

第 1 轮 3 被杀掉,剩余 {1, 2, 【3】, 4, 5, 6, 7, 8, 9, 10}

第 2 轮 6 被杀掉,剩余 {1, 2, 【3】, 4, 5, 【6】, 7, 8, 9, 10}

第 3 轮 9 被杀掉,剩余 {1, 2, 【3】, 4, 5, 【6】, 7, 8, 【9】, 10}

第 4 轮 2 被杀掉,剩余 {1, 【2】, 【3】, 4, 5, 【6】, 7, 8, 【9】, 10}

第 5 轮 7 被杀掉,剩余 {1, 【2】, 【3】, 4, 5, 【6】, 【7】, 8, 【9】, 10}

第 6 轮 1 被杀掉,剩余 {【1】, 【2】, 【3】, 4, 5, 【6】, 【7】, 8, 【9】, 10}

第 7 轮 8 被杀掉,剩余 {【1】, 【2】, 【3】, 4, 5, 【6】, 【7】, 【8】, 【9】, 10}

第 8 轮 5 被杀掉,剩余 {【1】, 【2】, 【3】, 4, 【5】, 【6】, 【7】, 【8】, 【9】, 10}

第 9 轮 10 被杀掉,剩余 {【1】, 【2】, 【3】, 4, 【5】, 【6】, 【7】, 【8】, 【9】, 【10】}

第 10 轮 4 被杀掉,剩余 {【1】, 【2】, 【3】, 【4】, 【5】, 【6】, 【7】, 【8】, 【9】, 【10】}

3 代码

# include <stdio.h>
# include <stdlib.h>
# define N 10
# define OVERFLOW 0
int KeyW[10] = {1,2,3,4,5,6,7,8,9,10};


/**
功能:约瑟夫环
描述:使用循环链表实现约瑟夫环,给定一组结点数据 {1,2,3,4,5,6,7,8,9,10},然后从报数开始,进行打印
**/

typedef struct LNode {
	int keyword;
	struct LNode *next;
} LNode,*LinkList;

void Joseph(LinkList p,int m,int x) {
	LinkList q;											// 声明变量
	int i;
	if(x == 0)return;
	q = p;
	m%= x;
	if(m == 0) m = x;
	for(i = 1; i <= m; i++){								// 找到下一个结点
		p = q;
		q = p->next;
	}
	p->next = q->next;
	i = q->keyword;
	printf("\n出队元素: %d \t",q->keyword);
	printf("剩余元素: ");
	LinkList tmp =  p;
	int tmp_x = x;
	while(tmp && tmp_x > 1) {
		printf("%d -> ", tmp->keyword);
		tmp = tmp->next;
		tmp_x--;
	}
	free(q);
	Joseph(p, i, x-1);									// 递归调用
}

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

	int i,m;
	LinkList Lhead,p,q;
	Lhead = (LinkList)malloc(sizeof(LNode));			// 申请结点空间
	if(!Lhead) return OVERFLOW;
	Lhead->keyword = KeyW[0];							// 数据域赋值
	Lhead->next = NULL;
	p = Lhead;
	for(i = 1;i<10;i++){								// 创建循环链表
		if(!(q = (LinkList)malloc(sizeof(LNode))))return OVERFLOW;
		q->keyword = KeyW[i];
		p->next = q;
		p = q;
	}
	p->next = Lhead;
	printf("请输入第一次计数值m: \n");
	scanf("%d", &m);
	printf("\n输出的队列结点是:\n");
	Joseph(p, m, N);
	getchar();
	return 1;
}

示例结果:

$ gcc ex074.c -o demo
$ ./demo
请输入第一次计数值m:
3

输出的队列结点是:

出队元素: 3 	剩余元素: 2 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 1 ->
出队元素: 6 	剩余元素: 5 -> 7 -> 8 -> 9 -> 10 -> 1 -> 2 -> 4 ->
出队元素: 2 	剩余元素: 1 -> 4 -> 5 -> 7 -> 8 -> 9 -> 10 ->
出队元素: 5 	剩余元素: 4 -> 7 -> 8 -> 9 -> 10 -> 1 ->
出队元素: 1 	剩余元素: 10 -> 4 -> 7 -> 8 -> 9 ->
出队元素: 4 	剩余元素: 10 -> 7 -> 8 -> 9 ->
出队元素: 10 	剩余元素: 9 -> 7 -> 8 ->
出队元素: 7 	剩余元素: 9 -> 8 ->
出队元素: 8 	剩余元素: 9 ->
出队元素: 9 	剩余元素:

案例ex75: 创建顺序表并插入元素

1 题目

功能:创建顺序表并插入元素

描述:创建一个顺序表,插入元素,并且打印出来

2 思路

创建一个链表的结构体,规定了元素最大长度,循环将要插入的元素进行往链表中插入

3 代码

#include <stdio.h>
#include <stdlib.h>
#define Listsize 100

/**
功能:创建顺序表并插入元素
描述:创建一个顺序表,插入元素,并且打印出来
**/

struct sqlist {
    int data[Listsize];
    int length;
};

void InsertList(struct sqlist *l, int t, int i) {
    int j;
    if (i < 0 || i > l->length) {
        printf("位置错误");
        exit(1);
    }
    if (l->length >= Listsize) {						// 如果超出顺序表范围,则溢出

        printf("溢出");
        exit(1);
    }
    for (j = l->length - 1; j >= i; j--)				// 插入元素
        l->data[j + 1] = l->data[j];
    l->data[i] = t;
    l->length++;
}

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

    struct sqlist *sq;
    int i, n, t;
    sq = (struct sqlist*)malloc(sizeof(struct sqlist));	// 分配空间
    sq->length = 0;
    printf("请输入链表大小:");
    scanf("%d", &n);
    printf("请输入链表的元素:\n");
    for (i = 0; i < n; i++) {
        scanf("%d", &t);
        InsertList(sq, t, i);							// 插入元素
    } printf("这个链表现在是:\n");
    for (i = 0; i < sq->length; i++) {
        printf("%d ", sq->data[i]);
    }
    getchar();
}

示例结果:

$ gcc ex075.c -o demo
$ ./demo
请输入链表大小:10
请输入链表的元素:
1 2 3 4 5 6 7 8 9 10
这个链表现在是:
1 2 3 4 5 6 7 8 9 10 %

案例ex76: 合并两个链表

1 题目

功能:合并两个链表

描述:两个链表进行合并,将第二个单链表连接在第一个单链表的尾部

2 思路

通过不断的遍历第一个链表,知道链表尾部,即 L1->next = NULL,之后将 L1->next 指向 L2 的头结点

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:合并两个链表
描述:两个链表进行合并,将第二个单链表连接在第一个单链表的尾部
**/

typedef struct student {
    int num;
    struct student *next;
} LNode, *LinkList;

LinkList create() {
    LinkList head;
    LNode *p1,  *p2;
    char a;
    head = NULL;
    a = getchar();
    while (a != '\n') {
        p1 = (LNode*)malloc(sizeof(LNode));	// 分配空间
        p1->num = a;						// 数据域赋值
        if (head == NULL)
            head = p1;
        else
            p2->next = p1;
        p2 = p1;
        a = getchar();
    }
    p2->next = NULL;
    return head;
}

LinkList coalition(LinkList L1, LinkList L2) {
    LNode *temp;
    if (L1 == NULL)
        return L2;
    else {
        if (L2 != NULL) {
            for (temp = L1; temp->next != NULL; temp = temp->next);
            temp->next = L2; 				// 遍历L1中节点直到尾节点
        }
    }
    return L1;
}

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

    LinkList L1, L2, L3;
    printf("请输入两个链表:\n");
    printf("第一个链表是:\n");
    L1 = create();							// 创建一个链表
    printf("第二个链表是:\n");
    L2 = create();							// 创建第二个链表
    coalition(L1, L2);						// 连接两个链表
    printf("合并后的链表是:\n");
    while (L1){								// 输出合并后的链表*
        printf("%c", L1->num);
        L1 = L1->next;
    }
    getchar();
}

示例结果:

$ gcc ex076.c -o demo
$ ./demo
请输入两个链表:
第一个链表是:
apple and
第二个链表是:
orage
合并后的链表是:
apple and orage

案例ex77: 单链表节点逆置

1 题目

功能:单链表节点逆置

描述:创建一个单链表,实现将创建好的单链表进行逆置

2 思路

创建两个指向结点的临时指针,一个指向 NULL,另外一个执行头结点,然后不断向后遍历,进行原地链表反转

3 代码

#include <stdio.h>
#include <stdlib.h>


/**
功能:单链表节点逆置
描述:创建一个单链表,实现将创建好的单链表进行逆置
**/


struct Node {
    int num;
    struct Node *next;
};

struct Node *create(int n) {
    int i;
    struct Node *head,  *p1,  *p2;
    int a;
    head = NULL;
    printf("链表元素:\n");
    for (i = n; i > 0; --i) {
        p1 = (struct Node*)malloc(sizeof(struct Node)); 	// 分配空间

        scanf("%d", &a);
        p1->num = a;										// 数据域赋值
        if (head == NULL) {
            head = p1;
            p2 = p1;
        }
        else {
            p2->next = p1; 									// 指定后继指针
            p2 = p1;
        }
    }
    p2->next = NULL;
    return head;											// 返回头结点指针
}

struct Node *reverse(struct Node *head) {
    struct Node *p,  *r;
    if (head->next && head->next->next) {
        p = head;								// 获取头结点地址
        r = p->next;
        p->next = NULL;
        while (r) {
            p = r;
            r = r->next;
            p->next = head;
            head = p;
        } return head;
    }
    return head;								// 返回头结点
}

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

    int n, i;
    int x;
    struct Node *q;
    printf("输入你想创建的结点个数:");
    scanf("%d", &n);
    q = create(n);								// 创建单链表
    q = reverse(q);								// 单链表逆置
    printf("逆置后的单链表是:\n");
    while (q) {									// 输出逆置后的单链表
        printf("%d ", q->num);
        q = q->next;
    }
    getchar();
}

示例结果:

$ gcc ex077.c -o demo
$ ./demo
输入你想创建的结点个数:5
链表元素:
1
2
3
4
5
逆置后的单链表是:
5 4 3 2 1

案例ex78: 明码序列号保护

1 题目

功能:明码序列号保护

描述:使用明码序列号保护

2 思路

采用明码序列号保护是通过使用序列号对应用程序进行保护的最初级的方法

通过使用序列号对程序进行注册,获取使用程序某些功能的权限

采用明码序列号保护的方式是通过对用户输入的序列号与程序自动生成的合法序列号或内置序列号进行比较,采用这种方式并不是很安全,容易被截获到合法的序列号。

3 代码

#include <stdio.h>
#include <string.h>

/**
功能:明码序列号保护
描述:使用明码序列号保护
**/

int main(int argc, char const *argv[]) {
	char *ysn;													// 声明字符指针
	char *sn;
	printf("\nPlease input the serial number:\n");			// 指定合法序列号
	sn="1001-1618-2903";
	scanf("%s",ysn);
    if(!strcmp(ysn,sn))								// 进行序列号比较
    	printf("register succeed");			// 注册成功
    else
    	printf("register lose"); 				// 注册失败
    	exit();
}

示例结果:

$ gcc ex078.c -o demo
$ ./demo

Please input the serial number:
123
register lose

Please input the serial number:
1001-1618-2903
register succeed

案例ex79: 非明码序列号保护

1 题目

功能:非明码序列号保护

描述:

本实例的验证算法是将序列号分为 4 段,每段 5 个字符,每段之间以字符“-”分隔。计算每段所有 ASCI 码的和,如果第一段 ASC 码的和模 6 的值为 1, 第二段 ASC 码的和模 8 的值为 1, 第三段 ASC 码的和模 9 的值为 2, 第四段 ASCⅡ码的和模 3 的值为 0, 那么该序列号视为合法,否则非法。

2 思路

采用菲明码序列号保护的方式验证序列号比采用明码序列号保护的方式安全。因为,非明码序列号保护是通过将输入的序列号进行算法验证实现的,而明码序列号保护是通过将输入的序列号与计算生成的合法序列号进行字符串比较实现的。采用明码序列号保护的程序在注册时会生成合法的序列号,该序列号可以通过内存设断的方式获取。一而采用非明码序列号保护的方式无法通过内在设断的方式获取。

本实例的验证算法是将序列号分为 4 段,每段 5 个字符,每段之间以字符“-”分隔。计算每段所有 ASCI 码的和,如果第一段 ASC 码的和模 6 的值为 1, 第二段 ASC 码的和模 8 的值为 1, 第三段 ASC 码的和模 9 的值为 2, 第四段 ASCⅡ码的和模 3 的值为 0, 那么该序列号视为合法,否则非法。

3 代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

/**
功能:非明码序列号保护
描述:
	本实例的验证算法是将序列号分为 4 段,每段 5 个字符,每段之间以字符“-”分隔。计算每段所有 ASCI 码的和,如果第一段 ASC 码的和模 6 的值为 1, 第二段 ASC 码的和模 8 的值为 1, 第三段 ASC 码的和模 9 的值为 2, 第四段 ASCⅡ码的和模 3 的值为 0, 那么该序列号视为合法,否则非法。
**/

int getsn1(char *str) {
    int i;
    int sum;
    sum=0;
    for(i=0;i<5;i++) {
    	sum=sum+toascii(str[i]);
    }
    printf("%d\n", sum%6);
    return sum;
}

int getsn2(char *str) {
    int i;
    int sum;
    sum=0;
    for(i=6;i<11;i++) {
    	sum=sum+toascii(str[i]);
    }
    printf("%d\n", sum%8);
    return sum;
}

int getsn3(char *str) {
    int i;
    int sum;
    sum=0;
    for(i=12;i<17;i++) {
    	sum=sum+toascii(str[i]);
    }
    printf("%d\n", sum%9);
    return sum;
}

int getsn4(char *str) {
    int i;
    int sum;
    sum=0;
    for(i=18;i<23;i++) {
    	sum=sum+toascii(str[i]);
    }
    printf("%d\n", sum%3);
    return sum;
}

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

	char str[23];
	printf("\nplease input the serial number:\n");
    scanf("%s", str);
    if(strlen(str)==23 && str[5]=='-' && str[11]=='-' && str[17]=='-') {
    	if(getsn1(str)%6==1 && getsn2(str)%8==1 && getsn3(str)%9==2 && getsn4(str)%3==0) {
    		printf("%s\n", "register succeed");
    		return 0;
    	} else{
    		printf("%s\n", "register Lose");
    		return -1;
    	}
    } else {
    	printf("%s\n", "register Lose");
    	return -1;
    }

}

示例结果:

$ gcc ex070.c -o demo
$ ./demo

please input the serial number:
11113-22221-33332-11112
register succeed

$ ./demo
please input the serial number:
11113-22221-33332-11111

register Lose

案例ex80: 恺撒加密

1 题目

功能:恺撒加密

描述:利用凯撒密码进行加密

2 凯撒密码

维基百科对凯撒密码的解释: https://zh.wikipedia.org/wiki/凱撒密碼

凯撒密码是一种替换加密技术,明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。例如,当偏移量是3的时候,所有的字母A将被替换成D,B变成E,以此类推

例如,当偏移量是左移3的时候(解密时的密钥就是3):

明文字母表:ABCDEFGHIJKLMNOPQRSTUVWXYZ
密文字母表:DEFGHIJKLMNOPQRSTUVWXYZABC

3 代码

# include <stdio.h>
# include <string.h>

/**
功能:恺撒加密
描述:利用凯撒密码进行加密
**/

void encode(char str[], int n) {
    char c;
    int i;
    for (i = 0; i < strlen(str); i++) {
        c = str[i];
        if (c >= 'a' && c <= 'z')
            if (c + n % 26 <= 'z')
                str[i] = (char)(c + n % 26);
            else
                str[i] = (char)('a' + ((n - ('z' - c) - 1) % 26));
            else if (c >= 'A' && c <= 'Z')
                if (c + n % 26 <= 'Z')
                    str[i] = (char)(c + n % 26);
                else
                    str[i] = (char)('A' + ((n - ('Z' - c) - 1) % 26));
                else
                    str[i] = c;
    }
    printf("\nout:");
    puts(str);

}

void decode(char str[], int n) {
    char c;
    int i;
    for (i = 0; i < strlen(str); i++) {
        c = str[i];
        if (c >= 'a' && c <= 'z')
            if (c - n % 26 >= 'a')
                str[i] = (char)(c - n % 26);
            else
                str[i] = (char)('z' - (n - (c - 'a') - 1) % 26);
            else if (c >= 'A' && c <= 'Z')
                if (c - n % 26 >= 'A')
                    str[i] = (char)(c - n % 26);
                else
                    str[i] = (char)('Z' - (n - (c - 'A') - 1) % 26);
                else
                    str[i] = c;
    }
    printf("\nout:");
    puts(str);
}

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

    void encode(char str[], int n);
    void decode(char str[], int n);
    //char str[]="abcdef";
    char str[20];
    int k = 0, n = 0, i = 1;
    printf("\nPlease input strings:");
    scanf("%s", str);

    printf("\n1:Encryption");
    printf("\n2:Decryption");
    printf("\n3:Violent Crack");
    printf("\nPlease choose:");
    scanf("%d", &k);


    if (k == 1) {
        printf("\nPlease input number:");
        scanf("\n%d", &n);
        encode(str, n);
    }
    else if (k == 2) {
        printf("\nPlease input number:");
        scanf("%d", &n);
        decode(str, n);
    }
    else if (k == 3) {
        for (i = 1; i <= 25; i++) {
            printf("%d ", i);
            decode(str, 1);
        }
    }
}

示例结果:

$ gcc ex080.c -o demo
$ ./demo

Please input strings:python

1:Encryption
2:Decryption
3:Violent Crack
Please choose:1

Please input number:1

out:qzuipo

该例子中有以下三种选择,结合上述对于凯撒密码的原理,试着进行理解

  • Encryption
  • Decryption
  • Violent Crack

案例ex81: 编写循环移位函数

1 题目

功能:编写循环移位函数

描述:编写个移位函数,使移位函数既能循环左移又能循环右移。参数 n 大于 0 时表示左移,参数 n 小于 0 时表示右移。例如 n=-4, 表示要右移四位

2 思路

利用 << 或者 >> 进行移位计算

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:编写循环移位函数
描述:编写个移位函数,使移位函数既能循环左移又能循环右移。参数 n 大于 0 时表示左移,参数 n 小于 0 时表示右移。例如 n=-4, 表示要右移四位
**/

unsigned move(unsigned value, int n)	{								// 自定义移位函数

    unsigned z;
	if(n>0) {
		z = (value >> (32-n)) | (value << n);				// 循环左移的实现过程
	}
	else {
		n=-n;
		z = (value << (32-n)) | (value >> n);				// 循环右移的实现过程
	}
	return z;
}

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

    unsigned a;
    int n;
    printf("请输入一个八进制数:\n");
    scanf("%o", &a);																// 输入一个八进制数
    printf("请输入要移位的位数:\n");
    scanf("%d", &n);																	// 输入要移位的位数
    printf("移位后的结果是:%o\n", move(a, n));						// 将移位后的结果输出
}

示例结果:

请输入一个八进制数:
12
请输入要移位的位数:
1
移位后的结果是:24

案例ex82: 取出给定 16 位二进制数的奇数位

1 题目

功能:取出给定 16 位二进制数的奇数位

描述:取出给定的 16 位二进制数的奇数位,计算后打印出来

2 思路

本例的解题关键在于如何将给定的 16 位二进制数的奇数位取出。首先定义个可以借助的中间变量 m,为其赋值,使箕成为最高位是1、其余 15 位为 0 的 16 位进制数

将给定的数 a 的值左移一位,让其原来值的第 15 位成为最高位,将 a 和 m 进行与运算进行判断,若运算结果是 1, 则将此位取出转换为对应的十进制数:若运算结果是 0, 则将 a 的值左移两位,使其原来值的第 13 位成为最高位,再进行判断,直到将 8 位奇数位全部取出

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:取出给定 16 位二进制数的奇数位
描述:取出给定的 16 位二进制数的奇数位,计算后打印出来
**/

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

    unsigned short a,s=0,q;
    int i,j,n=7,m;
	printf("请输入一个八进制数:\n");
    scanf("%ho", &a);								// 输入一个八进制数
    m=1<<15;										// m 的最高位为 1,其他位为 0
	a<<=1;											// 左移一位,使第15位成为最高位
	for(i=1;i<=8;i++) {								// 得到 8 位数
		
		q=1;
		if(m & a) {									// 如果本位上值为1则进行计算
			
			for(j=1;j<=n;j++)
				q*=2;								// 得到权值
			s+=q;									// 累加
		}
		a<<=2;										// 向左移位
		n--;	
	}
    printf("结果是:%o\n", s);						// 将结果输出
}

示例结果:

$ gcc ex082.c -o demo
$ ./demo

请输入一个八进制数:
013
结果是:1

案例ex83: 求一个数的补码

1 题目

功能:求一个数的补码

描述:输入一个八进制数,然后输出它的补码

2 思路

一个正数的补码等于该数原码,一个负数的补码等于该数的反码加 1。

本例的关键是如何判断一个数是正数还是负数。当最高位为 1 时,则该数是负数;当最高位为 0 时,则该数是正数。

因此,数据 a 和八进制数据 010000 进行与运算,保留最高位得到数据的正负

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:求一个数的补码
描述:输入一个八进制数,然后输出它的补码
**/

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

    unsigned short  a,z;
	printf("请输入一个八进制数:\n");
    scanf("%ho", &a);							// 输入一个八进制数
	z=a & 0100000;									// 0100000的二进制形式为最高位为1,其余为0
	if(z==0100000)									// 如果a小于0
		z=~a+1;												// 取反加1
	else
		z=a;	
    printf("结果是: %o\n", z);			// 将结果输出
}

示例结果:

$ gcc ex083.c -o demo
$ ./demo

请输入一个八进制数:
115333
结果是: 62445

案例ex84: 普通的位运算

1 题目

功能:普通的位运算

描述:

求下面各个位运算的值

a&c

b|d

a^d

~a

2 思路

运算符 含义 示例 举例
& 位AND x&y 如果x和y都为1,则得到1;如果x或y任何一个为0,或都为0,则得到0
| 位OR x|y 如果x或y为1,或都为1,则得到1;如果x和y都为0,则得到0
^ 位XOR x^y 如果x或y的值不同,则得到1;如果两个值相同,则得到0
~ 位NOT(I的补码) ~x 如果x为0,则得到1,如果x是1,则得到0

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:普通的位运算
描述:
	求下面各个位运算的值
	a&c
	b|d
	a^d
	~a
**/

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

    unsigned result;
    int a, b, c, d;
    a = 2;
    b = 4;
    c = 6;
    d = 8;
    printf("a=%d,b=%d,c=%d,d=%d", a, b, c, d);				// 输出变量a、b、c、d四个数的值
    result = a &c;											// a与c的结果赋给result
    printf("\na&c=%u\n", result);							// 将结果输出
    result = b | d; 										// b|d的结果赋给result
    printf("b|d=%u\n", result);								// 将结果输出
    result = a ^ d;											// a^d的结果赋给result
    printf("a^d=%u\n", result);								// 将结果输出
    result = ~a;											// ~a的结果赋给result
    printf("~a=%u\n", result);								// 将结果输出
}

示例结果:

$ gcc ex084.c -o demo
$ ./demo

a=2,b=4,c=6,d=8
a&c=2
b|d=12
a^d=10
~a=4294967293

案例ex85: 整数与 0 异或

1 题目

功能:整数与 0 异或

描述:计算输入整数与 0 异或

2 思路

异或:相同为 1,不同为 0

运算符 含义 示例 举例
^ 位 XOR x^y 如果 x 或 y 的值不同,则得到 1;如果两个值相同,则得到 0

3 代码

#include <stdio.h>
#include <stdlib.h>


/**
功能:整数与 0 异或
描述:计算输入整数与 0 异或
**/


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

    unsigned result; 								// 定义无符号数
    int a, b;
    printf("请输入a:");
    scanf("%d",&a);
    b=0;											// 与0异或
    printf("a=%d,b=%d", a, b);
    result = a^b; 									// 求整数与0异或的结果
    printf("\na^b=%u\n", result);
}

示例结果:

$ gcc ex085.c -o demo
$ ./demo

请输入a:10
a=10,b=0
a^b=10

案例ex86: 打印杨辉三角

1 题目

功能:打印杨辉三角

描述:打印给定行数的杨辉三角

2 思路

杨辉三角形

又称帕斯卡三角形、贾宪三角形、海亚姆三角形、巴斯卡三角形,是二项式系数的一种写法,形似三角形,在中国首现于南宋杨辉的《详解九章算法》得名,书中杨辉说明是引自贾宪的《释锁算书》,故又名贾宪三角形。

最基本的几个性质

每个数等于它上方两数之和

每行数字左右对称,由1开始逐渐变大

第n行的数字有n项

前n行共[(1+n)n]/2 个数

举例

打印出前 9 行的数字

3 代码

#include <stdio.h>
#define N 15

/**
功能:打印杨辉三角
描述:打印给定行数的杨辉三角
**/

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

    int i, j, k, n, a[N][N];
    while(1) {
    	printf("请输入要打印的行数[范围在1~15之间]:"); // 控制在 15 行之内
    	scanf("%d",&n);
	    if (n>=1 && n<=15){  				
	        break;
	    } else {
	    	printf("输入的行数不在规定范围内!\n");
	    	continue;
	    }	
    }

    printf("%d行杨辉三角打印如下:\n",n);
    for(i=1; i<=n; i++)
        a[i][1] = a[i][i] = 1;   			
    for(i=3; i<=n; i++)
        for(j=2; j<=i-1; j++)
            a[i][j]=a[i-1][j-1]+a[i-1][j];
    for(i=1; i<=n; i++){
        for(k=1; k<=n-i; k++)
            printf("   ");   				
        for(j=1; j<=i; j++)  				
            printf("%6d",a[i][j]);
        printf("\n");   					
    }
    printf("\n");
}

示例结果:

请输入要打印的行数[范围在1~15之间]:20
输入的行数不在规定范围内!
请输入要打印的行数[范围在1~15之间]:30
输入的行数不在规定范围内!
请输入要打印的行数[范围在1~15之间]:10
10行杨辉三角打印如下:
                                1
                             1     1
                          1     2     1
                       1     3     3     1
                    1     4     6     4     1
                 1     5    10    10     5     1
              1     6    15    20    15     6     1
           1     7    21    35    35    21     7     1
        1     8    28    56    70    56    28     8     1
     1     9    36    84   126   126    84    36     9     1

案例ex87: 循环显示随机数

1 题目

功能:循环显示随机数

描述:随机产出 10 个随机数

2 思路

在产出随机数的时候,需要将设置随机发生的种子 srand(n);

随后才能进行产出不同的随机数

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:循环显示随机数
描述:随机产出 10 个随机数
**/


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

	int i;
	int rd; // 随机发生数
	for (i = 0; i < 10; i++) {
		// 随机发生数的种子
		srand(i+5);
		// 产出随机发生数
		rd = rand();
		printf("种子 %d 产出的随机数为%d\n", i+5, rd);
	}
}

示例结果:

$ gcc ex087.c -o demo
$ ./demo

种子 5 产出的随机数为84035
种子 6 产出的随机数为100842
种子 7 产出的随机数为117649
种子 8 产出的随机数为134456
种子 9 产出的随机数为151263
种子 10 产出的随机数为168070
种子 11 产出的随机数为184877
种子 12 产出的随机数为201684
种子 13 产出的随机数为218491
种子 14 产出的随机数为235298

案例ex88: 阿姆斯特朗数

1 题目

功能:阿姆斯特朗数

描述:打印从100到999之间所有的阿姆斯特朗数

2 思路

阿姆斯特朗数

如果一个n位正整数等于其各位数字的n次方之和,则称该数为阿姆斯特朗数。

例如1^3 + 5^3 + 3^3 = 153

水仙花数

当n=3时,又称水仙花数,特指一种三位数,其各个数之立方和等于该数。

水仙花数共有4个,分别为:153、370、371、407

3 代码

#include <stdio.h>
#include <stdlib.h>

/**
功能:阿姆斯特朗数
描述:打印从1到10000之间所有的阿姆斯特朗数
**/

int main(int argc, char const *argv[]) {
    int i, j, k, n;
    for (i = 1; i < 10000; i++) { // 遍历从 1 到 10000 的阿姆斯特朗数

        j = i % 10; 				// 分离出个位上的数
        k = i / 10 % 10; 			// 分离出十位上的数
        n = i / 100; 				// 分离出百位上的数
        if (j *j * j + k * k * k + n * n * n == i) 	// 进行判断各位上的立方和是否等于遍历数本身
            printf("%5d", i);
    }
	printf("\n");
}

示例结果:

$ gcc ex088.c -o demo
$ ./demo

1^3 + 5^3 + 3^3 = 153
3^3 + 7^3 + 0^3 = 370
3^3 + 7^3 + 1^3 = 371
4^3 + 0^3 + 7^3 = 407
分享到: