C语言重难点知识梳理与常见题目/项目游戏解析

1.使用JetBrains_Clion编译环境搭建入门指南

2.VisualStudio运行后一闪而过和scanf函数的4996报错问题

1.一闪而过

VS右边的解决方案,找到对应的项目后:属性–>连接器–>系统–>子系统:控制台 (/SUBSYSTEM:CONSOLE)

在这里插入图片描述

在这里插入图片描述

2.scanf的error C4996

报错如图:
在这里插入图片描述
网上有很多手动修改属性配置的,试过后大多不起作用。后来修改了newc++file.cpp后会自动取消掉scanf的报错问题。
scanf原本书C语言标准输入函数,但是VS为了区分,就在编译器中修改了scanf为scanf_s。他们俩本质一样,只是名称被修改而已
修改步骤如下:

  1. 开始菜单中找到vs图标然后找到打开文件位置
    在这里插入图片描述

  2. 找到vs的快捷方式后继续右击鼠标属性找到文件路径
    在这里插入图片描述

  3. 找到安装的文件路径【我在安装时创建了一个Install文件夹来存储】
    在这里插入图片描述

  4. 找到安装的路径【我的界面如下】
    在这里插入图片描述

  5. VC
    在这里插入图片描述

  6. vcprojectitems
    在这里插入图片描述

  7. newc++file.cpp

  8. 因为有文件保护,因此不能直接修改。把此文件拖入桌面修改后在放回原位即可
    文件中添加如下代码:#define _CRT_SECURE_NO_WARNINGS 1
    在这里插入图片描述
    下次自动创建项目的时候会自动添加define的预编译,忽略掉scanf_s的报错提醒

3.C语言中数据的存储

1.数据类型介绍

char        //字符数据类型
short       //短整型
int         //整形
long        //长整型
long long   //更长的整形
float       //单精度浮点数
double      //双精度浮点数

2.类型的基本归类

1.整形家族

char 
	unsigned char
	signed char
short
	unsigned short [int]
	signed short [int]
int 
	unsigned int
	signed int
long
	unsigned long [int]
	signed long [int]

[ ]意思是定义的时候可以省略 unsigned short a 和 unsigned short int a意思一样

2.浮点数家族

float
double

3.构造类型

> 数组类型
> 结构体类型 struct
> 枚举类型 enum
> 联合类型 union

4.指针类型

int *pi;
char *pc;
float* pf;
void* pv;

5.空类型

void 表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型

``

3.整形在内存中的存储

一个变量的创建是要在内存中开辟空间的,空间的大小是根据不同的类型而决定的,但在内存的存储到底是如何实现的
比如:

int a = 20;
int b = -10;

原码、反码和补码。
三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位
三种表示方法各不相同。

名称 含义
原码 直接将二进制按照正负数的形式翻译成二进制就可以
反码 将原码的符号位不变,其他位依次按位取反就可以得到了
补码 反码+1就得到补码

正数的原、反、补码都相同。
对于整形来说:数据存放内存中其实存放的是补码。

为什么呢?
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理; 同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
在这里插入图片描述

通过VS的调试我们发现对于a和b分别存储的是补码。但是顺序有点不对劲。这是因为大小端的影响。
大小端介绍

名称 含义
大端(存储)模式 是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中
小端(存储)模式 是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中

为何会有大端和小端
为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一
个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
例如一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为高字节, 0x22为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

习题:设计一个小程序来判断当前机器的字节序

void check_sys(){
    
    
    int num = 1;
    char *p = #
    if ( *p == 1){
    
    
        printf("小端\n");
    }else{
    
    
        printf("大端\n");
    }
    /*
     * 00000000 00000000  00000000 00000001 -- 1的2进制源码
     * 4个2进制位等于1个16进制位
     * 00000001 -- 1的16进制源码
     * 按照小端模式存储:低权值位在低地址【地址左高右低】
     * 01000000
     * */
}

如果对于大小端仍有疑问,可以参考此代码调节结果图:
在这里插入图片描述
在这里插入图片描述

分析:0x12345678是一个16进制数,对于32位系统。最多有8个16进制数。因为8个16进制数代表了32跟地址线【8*4,1个16进制等于4个2进制】,因此不能超过8个。
由于char *指针只能访问1个字节内容,因此只会修改2个16进制位的开始地址,也就是7,8被改为0,0。

0x0133FC8C 78 0x0133FC8D 56 0x0133FC8E 34 0x0133FC8F 12
我们也发现地址是78开始向12递增,也就是地址线“左低右高”原则排序,也侧面证明是小端存储模式

4.数据溢出的坑

1.unsigned char VS signed char

    /*
     * 10000000 00000000  00000000 00000001 -- -1的2进制源码
     * 11111111 11111111  11111111 11111110 -- -1的2进制反码
     * 11111111 11111111  11111111 11111111 -- -1的2进制补码
     * char是1字节,补码发生截断:
     * 11111111 11111111
     * 又由于是unsigned,所以符号位也代表进制位,所以是2^8-1=255
     * */
    char a= -1;
    signed char b=-1;
    unsigned char c=-1;
    printf("a=%d,b=%d,c=%d",a,b,c);
    return 0;
输出结果:
	a=-1,b=-1,c=255

2.char的神秘±128转折

再啰嗦一下冷门的格式:

%? 含义
%u 十进制无符号整数
%e 指数形式的浮点数
%x, %X 无符号以十六进制表示的整数
%0 无符号以八进制表示的整数
%g 自动选择合适的表示法
    /*
     * 10000000 00000000 00000000 1000000 -- -128的2进制源码
     * 11111111 11111111 11111111 0111111 -- -128的2进制反码
     * 11111111 11111111 11111111 1000000 -- -128的2进制补码
     *
     * 00000000 00000000 00000000 1000000 -- 128的2进制源码
     * 01111111 11111111 11111111 0111111 -- 128的2进制反码
     * 01111111 11111111 11111111 1000000 -- 128的2进制补码
     *
     * 由于%u无符号输出10进制整形,因此会发生隐式的整型提升,提升的最高符号位后当作进制数输出
     * */
    char a = -128;
    char b = 128;
    printf("a=%u\n",a);
    printf("b=%u\n",b);
输出结果:
    a=4294967168
	b=4294967168

3. 按照补码形式计算,最后格式化为有符号整数

   int i = -20;
    unsigned int j = 10;
    printf("%d\n", i+j);
输出结果:
	-10

4. unsigned 的死循环

    unsigned int i;
    for (i = 9; i >= 0; --i) {
    
    
        printf("%u\n", i);
    }
输出结果【死循环】:
	D:\Progam\jetBrains\Clion\Test\cmake-build-debug\main.exe
9
8
7
6
5
4
3
2
1
0
4294967295
4294967294
4294967293
...
...
...

记住这个循环图,可以有效帮助理解char整型提升概念。避免被溢出或者unsigned的影响导致程序bug
在这里插入图片描述

5. 数组长度

    char a[1000];
    int i;
    for(i=0; i<1000; i++)
    {
    
    
        a[i] = -1-i;
    }
    printf("%d",strlen(a));
    return 0;
输出结果:
	255

i值变化: 0, 1, 2…1000
a[i]值变化: -1, -2, -3… -127, -128, 127, 126… 1, 0, -1, -2, -3…[由于是char类型,所以最大存储数值得范围是 -128~127],而strlen遇到 \0 就返回。因此当遇到 0 的时候已经存储了 255 个元素而返回255.但是数组中却存储了很多次 -128到127得循环值

5.浮点型在内存中的存储

    int n = 9;
    float *p = (float *)&n;
    printf("n: %d\n", n);
    printf("*p: %f\n", *p);

    *p = 9.0;
    printf("n: %d\n", n);
    printf("*p: %f\n", *p);
输出结果:
	n: 9
	*p: 0.000000
	n: 1091567616
	*p: 9.000000

n 和 *p在内存中明明是同一个数,为什么浮点数和整数的解读结果会差别这么大? 要理解这个结果,一定要搞懂浮点数在计算机内部的表示方法。
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:

  • (-1)^S * M * 2^E
  • (-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
  • M表示有效数字,大于等于1,小于2。
  • 2^E表示指数位

举例来说: 十进制的5.0,写成二进制是 101.0 ,相当于 1.01×2^2 。 那么,按照上面V的格式,可以得出s=0,M=1.01,E=2。
十进制的-5.0,写成二进制是 -101.0 ,相当于 -1.01×2^2 。那么,s=1,M=1.01,E=2。
IEEE 754规定: 对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。
在这里插入图片描述
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
在这里插入图片描述
**IEEE 754对有效数字M和指数E,还有一些特别规定。**前面说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。
IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字

至于指数E,情况就比较复杂

首先,E为一个无符号整数(unsigned int) 这意味着,如果E为8位,它的取值范围为0255;如果E为11位,它的取值范围为02047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。

然后E从内存中提取出还可以再分成三种情况:
E不全为0或不全为1
这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。 比如: 0.5(1/2)的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,则为1.0*2^(-1),其阶码为-1+127=126,表示为01111110,而尾数1.0去掉整数部分为0,补齐0到23位00000000000000000000000则其二进制表示形式为:
0 01111110 00000000000000000000000
E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值, 有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。

E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);好了,关于浮点数的表示规则,就说到这里。

解释前面的题目:
为什么 0x00000009 还原成浮点数,就成了 0.000000 ? 首先,将 0x00000009 拆
分,得到第一位符号位s=0,后面8位的指数 E=00000000 ,最后23位的有效数字M=000 0000 0000 0000 0000 1001。
9 -> 0000 0000 0000 0000 0000 0000 0000 1001
S E M格式:
0 00000000 000 0000 0000 0000 0000 1001
算式:
(-1)^0 * 0.000000000000000000010012^(-126)
简化:
1.001
2^(-146)
显然,这是一个很小且接近0得正数,因此%f格式会输出0.000000

浮点数9.0得二进制表示
9.0 -> 1001.0 ->(-1)^0*1.0012^3 -> s=0, M=1.001,E=3+127=130
130 = 127+3=128+2
0 10000010 001 0000 0000 0000 0000 0000
这个32位得2进制位还原成10进制,正是1091567616

4.C指针总结和进阶

1.指针的进阶

1.字符指针

	char *pstr = "Hello";

代码 char* pstr = “hello bit.”; 特别容易被大家误以为是把字符串 Hello 放到字符指针 pstr 里,但是本质是把字符串 Hello. 首字符的地址放到了pstr中。
在这里插入图片描述

上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量 pstr 中.

    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    char *str3 = "hello bit.";
    char *str4 = "hello bit.";
    if (str1 == str2)
        printf("str1 and str2 are same\n");
    else
        printf("str1 and str2 are not same\n");
    if (str3 == str4)
        printf("str3 and str4 are same\n");
    else
        printf("str3 and str4 are not same\n");
输出结果:
	str1 and str2 are not same
	str3 and str4 are same

这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域。当几个指针,指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。

2.指针数组

    int* arr1[10]; //整形指针的数组
    char *arr2[4]; //一级字符指针的数组
    char **arr3[5];//二级字符指针的数组

3.数组指针

1.数组指针定义

数组指针是指针?还是数组?
答案是:指针
我们已经熟悉: 整形指针: int * pint; 能够指向整形数据的指针。 浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。

    int *p1[10];
    int (*p2)[10];
    //p1, p2分别是什么?

解释:
int *p1[10]: [ ]优先级比 * 优先级高,因此p1是一个数组名,而int *定义了p1的数组类型。因此p1是一个能存放10个整形指针的数组

int (*p2)[10]; p2先和*结合,因此p2是一个指针名,p2取走后只有 int (*)[10]说明是一个指针,和上文中定义整型指针,浮点型指针格式一样int *pint, int *pfloat。因此p2是一个指向能存放10个元素的整型指针数组

2.数组名VS数组名
    int arr[10];

arr和&arr分别是什么?
我们知道arr是数组名,数组名表示数组首元素地址。那么&arr数组名又是什么呢?
我们看一段代码:

    int arr[10] = {
    
    0};
    printf("%p\n", arr);
    printf("%p\n", &arr);
输出结果:
	0xffffcc10
	0xffffcc10

可见数组名和&数组名打印的地址是一样的。
难道两个是一样的吗?
我们再看一段代码:

    int arr[10] = {
    
     0 };
    printf("arr = %p\n", arr);
    printf("&arr= %p\n", &arr);
    printf("arr+1 = %p\n", arr+1);
    printf("&arr+1= %p\n", &arr+1);
输出结果:
	arr = 0xffffcc10
	&arr= 0xffffcc10
	arr+1 = 0xffffcc14
	&arr+1= 0xffffcc38

根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40。

3.数组值的使用

既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

    int arr[10] = {
    
    1,2,3,4,5,6,7,8,9,0};
    int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
    //但是我们一般很少这样写代码
#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
    
    
    int i = 0;
    for(i=0; i<row; i++)
    {
    
    
        for(int j=0; j<col; j++)
        {
    
    
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}
void print_arr2(int (*arr)[5], int row, int col)
{
    
    
    int i = 0;
    for(i=0; i<row; i++)
    {
    
    
        for(int j=0; j<col; j++)
        {
    
    
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}
int main()
{
    
    
    int arr[3][5] = {
    
    1,2,3,4,5,6,7,8,9,10};
    print_arr1(arr, 3, 5);
    //数组名arr,表示首元素的地址
    //但是二维数组的首元素是二维数组的第一行
    //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
    //可以数组指针来接收
    print_arr2(arr, 3, 5);
    return 0;
}
输出结果:
	1 2 3 4 5 
	6 7 8 9 10 
	0 0 0 0 0 
	
	1 2 3 4 5 
	6 7 8 9 10 
	0 0 0 0 0 

int arr[5]:能存放5个整型元素的整型数组
int *parr1[10]:能存放10个整型指针的指针数组
int (*parr2)[10]:指向10个整型元素的数组指针
int (*parr3[10])[5]:稍微复杂,我们把 parr3[10] 去掉后看 int (*)[5] 是不是很像我们的指针数组,它的内部是 *parr3[10]的一个指针数组。因此它是一个指向能够存放5个这样能够存放10个整型指针数组的的数组指针。

4.数组参数、指针参数
1.一维数组传参
#include <stdio.h>

void test(int arr[]){
    
    }//ok


void test(int arr[10]){
    
    }//ok


void test(int *arr){
    
    }//ok:一维数组传参,降维成一级指针变量地址


void test2(int *arr[20]){
    
    }//ok:数组,20个元素,每个元素是int *。因此符合int *arr[20]


void test2(int **arr){
    
    }//ok:二级指针接收指针数组,数组名是地址,数组存的是int *的指针,然后二级指针再接着解引用,即可访问数组内容

int main() {
    
    
    int arr[10] = {
    
    0};
    int *arr2[20] = {
    
    0};
    test(arr);
    test2(arr2);
}

2.二维数组传参
#include <stdio.h>

void test(int arr[3][5])//ok
{
    
    }

void test(int arr[][])//error
{
    
    }

void test(int arr[][5])//ok?
{
    
    }

//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//error
{
    
    }

void test(int *arr[5])//ok
{
    
    }

void test(int (*arr)[5])//ok *arr是指针,指向一个数组,数组有5个元素,每个元素是int
{
    
    }

void test(int **arr)//error:综合考虑,二级指针应该存放一级指针变量地址。但是这里的arr[3][5]是二维数组,二维数组传参会发生将为成一维数组,一级指针不能存放一维数组,只能存放一维数组降维后的指针变量地址
{
    
    }

int main() {
    
    
    int arr[3][5] = {
    
    0};
    test(arr);
}
3.一级指针传参
#include <stdio.h>
void print(int *p, int sz)
{
    
    
    int i = 0;
    for(i=0; i<sz; i++)
    {
    
    
        printf("%d ", *(p+i));
    }
}
int main()
{
    
    
    int arr[10] = {
    
    1,2,3,4,5,6,7,8,9};
    int *p = arr;
    int sz = sizeof(arr)/sizeof(arr[0]);
    //一级指针p,传给函数
    print(p, sz);
    return 0;
}
输出结果:
	1 2 3 4 5 6 7 8 9 0 

思考:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

#include <stdio.h>
void test1(int *p)  {
    
    }
int main()
{
    
    
    int arr[10] = {
    
    1,2,3,4,5,6,7,8,9};
    int *parr = arr;
    int num = 1;
    int *pnum = &num;
    test1(parr);
    test1(pnum);
    test1(arr);
    test1(&num);
    return 0;
}

总结为:一级指针本身,普通类型的一维数组名,变量地址

4.二级指针传参
#include <stdio.h>

void test(int **ptr) {
    
    
    printf("num = %d\n", **ptr);
}

int main() {
    
    
    int n = 10;
    int *p = &n;
    int **pp = &p;
    test(pp);
    test(&p);
    return 0;
}
输出结果:
	num = 10
	num = 10

思考:
当函数的参数为二级指针的时候,可以接收什么参数?

void test(char **p) {
    
    
}

int main() {
    
    
    char c = 'b';
    char *pc = &c;
    char **ppc = &pc;
    char *arr[10];
    test(&pc);
    test(ppc);
    test(arr);//Ok?
    return 0;
}

总结为:二级指针本身,一级指针地址,数组指针名

4.函数指针

#include <stdio.h>

void test() {
    
    
    printf("hehe\n");
}

int main() {
    
    
    printf("%p\n", test);
    printf("%p\n", &test);
    return 0;
}
输出结果:
	0x100401080
	0x100401080

输出的是两个地址,这两个地址是 test 函数的地址。 那我们的函数的地址要想保存起来,怎么保存?
下面我们看代码:

void test()
{
    
    
    printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *ppfun2();

首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针? 答案是:
pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。

阅读两段有趣的代码:

	//代码1
	(*(void (*)())0)();
	//代码2
	void (*signal(int , void(*)(int)))(int);
	//代码2的优化
	typedef void(*pfun_t)(int);
	pfun_t signal(int, pfun_t);

代码1
void (*)():说明是一个函数指针,返回值位void类型
(void (*)()) 0:是将内存地址中0x00000000的地址进行强制类型转换位函数指针类型
* (void (*)())0 : 是将强制转换后的 0 内存地址进行解引用操作
(*(void (*)())0)():解引用后指向的内容在于 () 结合,说明是一个函数。
因此是一个函数调用

代码2
void()(int):是一个函数指针,返回值为void
signal(int , void(
)(int)):是一个函数
void (*)(int):说明signal函数的返回类型又是一个函数指针
因此是一个函数指针

1.函数指针数组

数组是一个存放相同类型数据的存储空间,我们已经了解了指针数组, 比如

	int *arr[10];
	//数组的每个元素是int*

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

	int (*parr1[10]])();//函数指针数组
	int *parr2[10]();//函数数组指针
	int (*)() parr3[10];//啥也不是
2.利用函数指针数组制作的计算器
#include <stdio.h>

int add(int a, int b) {
    
    
    return a + b;
}

int sub(int a, int b) {
    
    
    return a - b;
}

int mul(int a, int b) {
    
    
    return a * b;
}

int div(int a, int b) {
    
    
    return a / b;
}

int main() {
    
    
    int x, y;
    int input = 1;
    int ret = 0;
    int (*p[5])(int x, int y) = {
    
    0, add, sub, mul, div}; //转移表
    while (input) {
    
    
        printf("*************************\n");
        printf(" 1:add           2:sub \n");
        printf(" 3:mul           4:div \n");
        printf("*************************\n");
        printf("请选择:");
        scanf("%d", &input);
        if ((input <= 4 && input >= 1)) {
    
    
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = (*p[input])(x, y);
        } else
            printf("输入有误\n");
        printf("ret = %d\n", ret);
    }
    return 0;
}
3.指向函数指针数组的指针

指向函数指针数组的指针是一个 指针 指针指向一个 数组 ,数组的元素都是 函数指针 ;
如何定义?

#include <stdio.h>

void test(const char *str) {
    
    
    printf("%s\n", str);
}

int main() {
    
    
    //函数指针pfun
    void (*pfun)(const char *) = test;
    //函数指针的数组pfunArr
    void (*pfunArr[5])(const char *str);
    pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
    void (*(*ppfunArr)[10])(const char *) = &pfunArr;
    return 0;
}
4.函数指针–回调函数

回调函数定义
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

/*指针进行的my_qsort排序*/
/*qsort函数原型
void qsort(void *base,//待排序的数据地址
           size_t num, //待排序的数据个数
           size_t width, //待排序的单个数据字节数
           int (*cmp)(const void *e1, const void *e2)//指定排序方法
           );*/

void swap(char *dst, char *src, int sz){
    
    
    char tmp;
    while (sz--){
    
    
        tmp = *src;
        *src = *dst;
        *dst = tmp;
        ++src, ++dst;
    }
}
void my_qsort(void *base, size_t num, size_t width, int (*cmp)(const void *e1, const void *e2)){
    
    
    assert(base);
    assert(cmp);
    char *tmp = (char *)base;
    int flag = 1;
    for (int i = 1; i < num; ++i) {
    
    
        flag = 1;
        for (int j = 0; j < num - i; ++j) {
    
    
            if (cmp(tmp+j*width, tmp+(j+1)*width) > 0){
    
    
                flag = 0;
                swap(tmp+j*width, tmp+(j+1)*width, width);
            }
        }
    }
}
typedef struct Stu {
    
    
    char name[20];
    int age;
} Stu;

void bubble(int a[], int sz) {
    
    
    int flag = 1;
    for (int i = 1; i < sz; ++i) {
    
    
        flag = 1;
        for (int j = 0; j < sz - i; ++j) {
    
    
            if (a[j] > a[j + 1]) {
    
    
                flag = 0;
                a[j] ^= a[j + 1] ^= a[j] ^= a[j + 1];
            }
        }
        if (flag) {
    
    
            break;
        }
    }
}

int cmp_int(const void *e1, const void *e2) {
    
    
    return *(int *) e1 - *(int *) e2;
}

int cmp_float(const void *e1, const void *e2) {
    
    
    if (*(float *) e1 > *(float *) e2) {
    
    
        return 1;
    } else if (*(float *) e1 < *(float *) e2) {
    
    
        return -1;
    } else {
    
    
        return 0;
    }
}

int cmp_Stu_by_age(const void *e1, const void *e2) {
    
    
    return ((Stu *)e1)->age - ((Stu *)e2)->age;
}
int cmp_Stu_by_name(const void *e1, const void *e2) {
    
    
    return (strcmp(((Stu *)e1)->name, ((Stu *)e2)->name));
}

模拟实现的C语言自带的 qsort 函数功能【本质是冒泡排序】
普通的冒泡排序只能排列一部分数据类型,如果用上结构体,字符串的话将会无法排序。
这段程序由通过void *指针来接收任何数据类型,再通过 char * 每次只能访问 1字节 的限制,再通过指定的排序方法【cmp_Stu_by_age,cmp_Stu_by_name,cmp_float,cmp_int】即可达到排序任何数据类型的目的。

2.笔试题

1.数组笔试题解析

	//一维数组
	int a1[] = {
    
     1, 2, 3, 4 };
	printf("%d\n", sizeof(a1));//16
	printf("%d\n", sizeof(a1 + 0));//4/8
	printf("%d\n", sizeof(*a1));//4
	printf("%d\n", sizeof(a1 + 1));//4/8
	printf("%d\n", sizeof(a1[1]));//4
	printf("%d\n", sizeof(&a1));//4/8
	printf("%d\n", sizeof(*&a1));//16
	printf("%d\n", sizeof(&a1 + 1));//4/8
	printf("%d\n", sizeof(&a1[0]));//4/8
	printf("%d\n", sizeof(&a1[0] + 1));//4/8

	//字符数组
	char ac1[] = {
    
     'a', 'b', 'c', 'd', 'e', 'f' };
	printf("%d\n", sizeof(ac1));//6
	printf("%d\n", sizeof(ac1 + 0));//4/8
	printf("%d\n", sizeof(*ac1));//1
	printf("%d\n", sizeof(ac1[1]));//1
	printf("%d\n", sizeof(&ac1));//4/8
	printf("%d\n", sizeof(&ac1 + 1));//4/8
	printf("%d\n", sizeof(&ac1[0] + 1));//4/8
	printf("%d\n", strlen(ac1));//?
	printf("%d\n", strlen(ac1 + 0));//?
	//    printf("%d\n", strlen(*ac1));//a的ASCII是97,16进制00000061地址被非法访问报错
	//    printf("%d\n", strlen(ac1[1]));//与strlen(*ac1)报错原一样,非法访问0x00000062地址
	printf("%d\n", strlen(&ac1));//?
	printf("%d\n", strlen(&ac1 + 1));//?-6
	printf("%d\n", strlen(&ac1[0] + 1));//?-1

	char ac2[] = "abcdef";
	printf("%d\n", sizeof(ac2));//7
	printf("%d\n", sizeof(ac2 + 0));//4/8
	printf("%d\n", sizeof(*ac2));//1
	printf("%d\n", sizeof(ac2[1]));//1
	printf("%d\n", sizeof(&ac2));//4/8
	printf("%d\n", sizeof(&ac2 + 1));//4/8
	printf("%d\n", sizeof(&ac2[0] + 1));//4/8
	printf("%d\n", strlen(ac2));//6
	printf("%d\n", strlen(ac2 + 0));//6
	//    printf("%d\n", strlen(*ac2));//error
	//    printf("%d\n", strlen(ac2[1]));//error
	printf("%d\n", strlen(&ac2));//6
	printf("%d\n", strlen(&ac2 + 1));//?
	printf("%d\n", strlen(&ac2[0] + 1));//5

	//常量字符串
	char *p = "abcdef";
	printf("%d\n", sizeof(p));//4/8
	printf("%d\n", sizeof(p + 1));//4/8
	printf("%d\n", sizeof(*p));//1
	printf("%d\n", sizeof(p[0]));//1
	printf("%d\n", sizeof(&p));//4/8
	printf("%d\n", sizeof(&p + 1));//4/8
	printf("%d\n", sizeof(&p[0] + 1));//4/8
	printf("%d\n", strlen(p));//6
	printf("%d\n", strlen(p + 1));//5
	//    printf("%d\n", strlen(*p));//error
	//    printf("%d\n", strlen(p[0]));//error
	printf("%d\n", strlen(&p));//?
	printf("%d\n", strlen(&p + 1));//?
	printf("%d\n", strlen(&p[0] + 1));//5

	//二维数组
	int a[3][4] = {
    
     0 };
	printf("%d\n", sizeof(a));//48
	printf("%d\n", sizeof(a[0][0]));//4
	printf("%d\n", sizeof(a[0]));//16
	printf("%d\n", sizeof(a[0] + 1));//4/8--a[1]
	printf("%d\n", sizeof(*(a[0] + 1)));//4
	printf("%d\n", sizeof(a + 1));//4/8--a[1]
	printf("%d\n", sizeof(*(a + 1)));//16
	printf("%d\n", sizeof(&a[0] + 1));//4/8--a[1]
	printf("%d\n", sizeof(*(&a[0] + 1)));//16
	printf("%d\n", sizeof(*a));//16
	printf("%d\n", sizeof(a[3]));//16--并不访问真实的a[3],而是访问的是数据类型

总结: 数组名的意义:

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
  3. 除此之外所有的数组名都表示首元素的地址。

2.指针笔试题

1.指针和数组±1
void t1() {
    
    
    /*
     * &a+1:代表以数组本身为地址,跳过一个数组长度后的地址。虽然被(int *)强转,但是-1并没有去减少4个字节的访问而是减去的越过的数组长度后,最终指向5的地方
     * (a+1):代表a[1]
     * */
    int a[5] = {
    
    1, 2, 3, 4, 5};
    int *ptr = (int *) (&a + 1);
    printf("%d,%d", *(a + 1), *(ptr - 1));
}
输出结果:
	2,5
2.地址,指针混合±1
void t2() {
    
    
    struct Test {
    
    
        int Num;
        char *pcName;
        short sDate;
        char cha[2];
        short sBa[4];
    } *p;
    p = (struct Test *) 0x100000;
    printf("%p\n", p + 0x1);//0x100014:struct Test *类型的指针+1就是越过20字节长度
    printf("%p\n", (unsigned long) p + 0x1);//0x100001:转换为unsigned long后+1就是普普通通+1。【0x00000005 + ox1--》6--》0x00000006】 
    printf("%p\n", (unsigned int *) p + 0x1);//0x100004:转换为指针类型后,+1就是越过指针长度【32为系统+4,64为系统+8】
}
输出结果:
	0x100014
	0x100001
	0x100004
3.字节空间的深入引用
void t3() {
    
    
    /*
     * 考点:大小端存储,指针地址±1,&数组名
     * &a + 1:以&a【整个数组】为基础地址,越过a数组长度后的地址
     * (int)a+1:将a地址转为int型,假设01的地址为0x00000005,(int)强转后为5,+1后就是6.再转为16进制地址0x0000006。0x00000006-0x00000005 = 1,差1就是差 1 字节
     * */
    int a[4] = {
    
    2, 2, 3, 4};
    int *ptr1 = (int *) (&a + 1);
    int *ptr2 = (int *) ((int) a + 1);
    printf("%x,%x\n", ptr1[-1], *ptr2);
}
输出结果:
	4,2000000

在这里插入图片描述

4.逗号表达式与指针
void t4(){
    
    
    int a[3][2] = {
    
     (0, 1), (2, 3), (4, 5) };
    int *p;
    p = a[0];
    printf( "%d", p[0]);
}
输出结果:
	1
5.指针,数组名的±1
void t5(){
    
    
    int a[5][5];
    int(*p)[4];
    p = a;
    printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
}

输出结果:
	0xfffffffffffffffc,-4

在这里插入图片描述

6.&数组名和数组名±1
t6(){
    
    
	/*
     * ptr1-1,减去的是等长的aa数组长度后的地址。最后指向元素10的地址
     * */
    int aa[2][5] = {
    
     1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int *ptr1 = (int *)(&aa + 1);
    int *ptr2 = (int *)(*(aa + 1));
    printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
}
输出结果:
	10,5

在这里插入图片描述

7.二级指针自增
void t7(){
    
    
    char *a[] = {
    
    "work", "at", "alibaba"};
    char **pa = a;
    printf("%s\n", *++pa);
}
输出结果:
	at

在这里插入图片描述

8.一,二,三级指针混合
void t8(){
    
    
    /*
     * 1.
     * **++cpp:++cpp后指向cp的c+2
     * *++cpp:解引用后访问cpp指向的cp中的c+2
     * **++cpp:解引用后访问cp指向的c中的POINT
     * 【此时cpp指向c+2】
     *
     * 2.
     * ++cpp:指向cp中的c+1
     * *++cpp:解引用后访问cpp指向cp中的c+1
     * --*++cpp:c+1指向的内容再-1【由"NEW"变为"ENTER"】
     * *--*++cpp:解引用后访问c+1指向的内容
     * *--*++cpp+3:以"ENTER"为基础,跳过3个字节
     * 【此时cpp指向c+1】
     *
     * 3.
     * cpp[-1][-1]+1
     * cpp[-1] == *(cpp-1)
     * cpp[-1][-1] == *(*(cpp-1)-1)
     * cpp-1:cpp指向cp中c+2
     * *(cpp-1)-1:cp指向c中的地址再-1【"POINT"变为"NEW"】
     * *(*(cpp-1)-1)+1:"NEW"越过一字节内容得"EW"
     * */
    char *c[] = {
    
    "ENTER","NEW","POINT","FIRST"};
    char**cp[] = {
    
    c+3,c+2,c+1,c};
    char***cpp = cp;
    printf("%s\n", **++cpp);//POINT
    printf("%s\n", *--*++cpp+3);//ER
    printf("%s\n", *cpp[-2]+3);//ST
    printf("%s\n", cpp[-1][-1]+1);//EW
}
输出结果:
	POINT
	ER
	ST
	EW

在这里插入图片描述

5.C语言通讯录详庖丁解牛【结构体基础版和动态内存,文件存储升级版】

1.结构体基础版

如果基础班无法运行,则需要将对应编译器的堆栈保留空间调大。因为基础版用的是一个结构体类型的数据,结构体数组中存储1000个这样的结构体。对于大多数编译器会造成堆栈溢出。

通讯录功能有增,删,查,改,排序,销毁,退出

  1. contact.h
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>

#define MAX 1000
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 12
#define MAX_ADDR 30

typedef struct PeoInfo{
    
    
	char name[MAX_NAME];
	int age;
	char sex[MAX_SEX];
	char tele[MAX_TELE];
	char addr[MAX_ADDR];
}PeoInfo;

typedef struct Contact{
    
    
	PeoInfo data[MAX];//结构体数组为基础创建
	int sz;//记录当前通讯录数据量
}Contact;

enum{
    
    
	EXIT,//0
	ADD,//1
	DEL,
	SEARCH,
	MODIFY,
	SHOW,
	SORT,
};

void InitContact(Contact *ps);

void AddContact(Contact *ps);

void ShowContact(const Contact *ps);

void DelContact(Contact *ps);

void SearchContact(const Contact *ps);

void ModifyContact(Contact *ps);

void SortContact(Contact *ps);

  1. contact.c
#include "contact.h"

void InitContact(Contact *ps){
    
    
    memset(ps->data, 0, sizeof(ps->data));
    ps->sz = 0;
}

void AddContact(Contact *ps){
    
    
    if (ps->sz >= MAX){
    
    
        printf("OverFllow!\n");
    }
    else{
    
    
        printf("Input name:");
        scanf("%s", ps->data[ps->sz].name);
        printf("Input age:");
        scanf("%d", &ps->data[ps->sz].age);
        printf("Input sex:");
        scanf("%s", ps->data[ps->sz].sex);
        printf("Input tele:");
        scanf("%s", ps->data[ps->sz].tele);
        printf("Input addr:");
        scanf("%s", ps->data[ps->sz].addr);
        ps->sz++;
        printf("AddContact success!\n");
    }
}

void ShowContact(const Contact *ps){
    
    
    if (ps->sz == 0){
    
    
        printf("Contact Empty!\n");
    }
    else{
    
    
        printf("%-7s\t%-4s\t%-5s\t%-12s\t%-20s\n", "名字", "年龄", "性别", "电话", "地址");
        for (int i = 0; i < ps->sz; ++i){
    
    
            printf("%-7s\t%-4d\t%-5s\t%-12s\t%-20s\n",
                   ps->data[i].name,
                   ps->data[i].age,
                   ps->data[i].sex,
                   ps->data[i].tele,
                   ps->data[i].addr);
        }
    }
}

static int FindByName(const struct Contact *ps, char *name){
    
    
    int i;
    for (i = 0; i < ps->sz; i++)
    {
    
    
        if (0 == strcmp(name, ps->data[i].name)){
    
    
            return i;
        }
    }
    return -1;
}

void DelContact(Contact *ps){
    
    
    char name[MAX_NAME];
    printf("Input del name:");
    scanf("%s", &name);
    // 1.查找要删除的人在什么位置
    int pos = FindByName(ps, name);
    //2.删除
    if (pos == ps->sz){
    
    
        printf("no name!\n");
    }
    else{
    
    
        // 删除数据
        for (int j = pos; j < ps->sz - 1; ++j){
    
    
            ps->data[j] = ps->data[j + 1];
        }
        ps->sz--;
        printf("Del success!\n");
    }
}

void SearchContact(const Contact *ps){
    
    
    char name[MAX_NAME];
    printf("Input find name:");
    scanf("%s", &name);
    int pos = FindByName(ps, name);
    if (pos == -1){
    
    
        printf("No find!\n");
    }
    else{
    
    
        printf("%-7s\t%-4s\t%-5s\t%-12s\t%-20s\n", "名字", "年龄", "性别", "电话", "地址");
        printf("%-7s\t%-4d\t%-5s\t%-12s\t%-20s\n",
               ps->data[pos].name,
               ps->data[pos].age,
               ps->data[pos].sex,
               ps->data[pos].tele,
               ps->data[pos].addr);
    }
}

void ModifyContact(Contact *ps){
    
    
    char name[MAX_NAME];
    printf("Input del name:");
    scanf("%s", &name);
    // 1.查找要删除的人在什么位置
    int pos = FindByName(ps, name);
    if (pos == -1){
    
    
        printf("No find!\n");
    }
    // 2.修改
    printf("Input name:");
    scanf("%s", ps->data[pos].name);
    printf("Input age:");
    scanf("%d", &ps->data[pos].age);
    printf("Input sex:");
    scanf("%s", ps->data[pos].sex);
    printf("Input tele:");
    scanf("%s", ps->data[pos].tele);
    printf("Input addr:");
    scanf("%s", ps->data[pos].addr);
    printf("ModifyContact success!\n");
}

void swap(char *dst, char *src, int sz){
    
    
    char tmp;
    while (sz--){
    
    
        tmp = *dst;
        *dst = *src;
        *src = tmp;
        ++dst, ++src;
    }
}

void my_qsort(void *base, int num, int width, int(*cmp)(const void *e1, const void *e2)){
    
    
    assert(base && cmp);
    char *tmp = (char *)base;
    int flag = 1;
    for (int i = 0; i < num; ++i){
    
    
        flag = 1;
        for (int j = 0; j < num - 1; ++j){
    
    
            if (cmp(tmp + j*width, tmp + (j + 1)*width) > 0){
    
    
                flag = 0;
                swap(tmp + j*width, tmp + (j + 1)*width, width);
            }
        }
        if (!flag){
    
    
            break;
        }
    }
}

int cmp_PeoInfo_By_Name(const void *e1, const void *e2){
    
    
    return strcmp(((PeoInfo *)e1)->name, ((PeoInfo *)e2)->name);
}

void SortContact(Contact *ps){
    
    
    my_qsort(ps->data, ps->sz, sizeof(ps->data[0]), cmp_PeoInfo_By_Name);
    printf("排序成功!\n");
}

  1. main.c
#include "contact.h"

void menu() {
    
    
    printf("*************************\n");
    printf("****1.add		2.del****\n");
    printf("****3.search	4.modify*\n");
    printf("****5.show		6.sort***\n");
    printf("****7.Destory	0.exit***\n");
    printf("*************************\n");
}

int main() {
    
    
    // 创建通讯录库
    struct Contact con[MAX];
    // 初始化通讯录
    InitContact(&con);
    int input = 0;
    do {
    
    
        menu();
        printf("Select:");
        scanf("%d", &input);
        switch (input) {
    
    
            case ADD:
                AddContact(&con);
                break;
            case DEL:
                DelContact(&con);
                break;
            case SEARCH:
                SearchContact(&con);
                break;
            case MODIFY:
                ModifyContact(&con);
                break;
            case SHOW:
                ShowContact(&con);
                break;
            case SORT:
                SortContact(&con);
                break;
            case EXIT:
                printf("Exit success!\n");
                break;
            default:
                printf("Select error!\n");
                break;
        }
    } while (input);
}

2.动态内存升级版【文件读写】

  1. contact.h
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>//用于结构体排序的strcmp字符串比较
#include <stdlib.h>//用于动态内存开辟malloc和realloc
#include <errno.h>// 用于动态内存开辟失败后贴出内存报错原因输出,errno
#include <assert.h>

#define MAX 1000
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 12
#define MAX_ADDR 30
#define DEFAULT_SZ 3

typedef struct PeoInfo{
    
    
	char name[MAX_NAME];
	int age;
	char sex[MAX_SEX];
	char tele[MAX_TELE];
	char addr[MAX_ADDR];
}PeoInfo;

typedef struct Contact{
    
    
	PeoInfo *data;//指向通讯录结构体的指针
	int sz;//当前通讯录的数据量
	int capacity;//当前通讯录的最大容量
}Contact;

//枚举类型数据不初始化会从0开始,是为了再main.c中表明对应的case的操作程序,使得代码更清晰明了
enum{
    
    
	EXIT,//0
	ADD,//1
	DEL,
	SEARCH,
	MODIFY,
	SHOW,
	SORT,
	DESTORY,
	SAVE,
};

//对应功能函数申明
// 结构体初始化
void InitContact(Contact *ps);
//增加数据
void AddContact(Contact *ps);
//打印数据
void ShowContact(const Contact *ps);
//删除数据
void DelContact(Contact *ps);
//查找数据
void SearchContact(const Contact *ps);
//修改数据
void ModifyContact(Contact *ps);
//排序通讯里【按照名称排序】
void SortContact(Contact *ps);
//清空通讯录
void DestoryContact(Contact *ps);
//写文件
void SaveContact(Contact *ps);
//加载文件信息到通讯录
void LoadContact(Contact *ps);



  1. contact.c
#include "contact.h"

void CheckCapacity(Contact *ps){
    
    
	if (ps->sz >= ps->capacity){
    
    
		// 增容
		PeoInfo *ptr = realloc(ps->data, (ps->capacity + 2)*sizeof(PeoInfo));//realloc将原有数据“保留”增容后初识结构体地址不变。每次扩容仅扩大2个PeoInfo结构体内容大小
		if (ptr != NULL){
    
    
			ps->data = ptr;
			ps->capacity += 2;
			printf("增容成功!\n");
		}
		else{
    
    
			printf("增容失败!\n");
		};
	}
}

void LoadContact(Contact *ps){
    
    
	FILE *pfRead = fopen("contact.dat", "rb");
	if (pfRead == NULL){
    
    
		printf("Load::%s\n", strerror(errno));
		return;
	}
	//读取文件
	PeoInfo tmp = {
    
     0 };
	while (fread(&tmp, sizeof(PeoInfo), 1, pfRead)){
    
    
		CheckCapacity(ps);
		ps->data[ps->sz] = tmp;
		ps->sz++;
	}
	fclose(pfRead);
	pfRead = NULL;
}

void InitContact(Contact *ps){
    
    
	ps->data = (PeoInfo *)malloc(DEFAULT_SZ * sizeof(PeoInfo));//动态内存开辟一段3倍于基础结构体PeoInfo大小的连续内存空间
	if (ps->data == NULL){
    
    //如果内存开辟失败,将在屏幕输出打印
		printf("%s\n", strerror(errno));
		return;
	}
	ps->sz = 0;//初始化的时候当前数据量为0
	ps->capacity = DEFAULT_SZ;//当前通讯录的最大容量
	//把文件已经存放的通讯录信息进行加载
	LoadContact(ps);
}

void AddContact(Contact *ps){
    
    
#if 0
	if (ps->sz == MAX){
    
    
		printf("OverFlow!\n");
	}
	else{
    
    
		printf("Input name:");
		scanf("%s", ps->data[ps->sz].name);
		printf("Input age:");
		scanf("%d", &ps->data[ps->sz].age);
		printf("Input sex:");
		scanf("%s", ps->data[ps->sz].sex);
		printf("Input tele:");
		scanf("%s", ps->data[ps->sz].tele);
		printf("Input addr:");
		scanf("%s", ps->data[ps->sz].addr);
		ps->sz++;
		printf("AddContact success!\n");
	}
#endif
	//1.检查当前通讯录的容量
	//1.1满了--》增加空间
	CheckCapacity(ps);
	//增加数据
	printf("Input name:");
	scanf("%s", ps->data[ps->sz].name);
	printf("Input age:");
	scanf("%d", &ps->data[ps->sz].age);
	printf("Input sex:");
	scanf("%s", ps->data[ps->sz].sex);
	printf("Input tele:");
	scanf("%s", ps->data[ps->sz].tele);
	printf("Input addr:");
	scanf("%s", ps->data[ps->sz].addr);
	ps->sz++;
	printf("AddContact success!\n");
}

void ShowContact(const Contact *ps){
    
    
	if (ps->sz == 0){
    
    
		printf("Contact Empty!\n");
	}
	else{
    
    
		printf("%-7s\t%-4s\t%-5s\t%-12s\t%-20s\n", "名字", "年龄", "性别", "电话", "地址");
		for (int i = 0; i < ps->sz; ++i){
    
    
			printf("%-7s\t%-4d\t%-5s\t%-12s\t%-20s\n",
				ps->data[i].name,
				ps->data[i].age,
				ps->data[i].sex,
				ps->data[i].tele,
				ps->data[i].addr);
		}
	}
}

static int FindByName(const Contact *ps, char *name){
    
    
	int i;
	for (i = 0; i < ps->sz; i++)
	{
    
    
		if (0 == strcmp(name, ps->data[i].name)){
    
    
			return i;
		}
	}
	return -1;
}

void DelContact(Contact *ps){
    
    
	char name[MAX_NAME];
	printf("Input del name:");
	scanf("%s", &name);
	// 1.查找要删除的人在什么位置
	int pos = FindByName(ps, name);
	//2.删除
	if (pos == ps->sz){
    
    
		printf("no name!\n");
	}
	else{
    
    
		// 删除数据
		for (int j = pos; j < ps->sz - 1; ++j){
    
    
			ps->data[j] = ps->data[j + 1];
		}
		ps->sz--;
		printf("Del success!\n");
	}
}

void SearchContact(const Contact *ps){
    
    
	char name[MAX_NAME];
	printf("Input find name:");
	scanf("%s", &name);
	int pos = FindByName(ps, name);
	if (pos == -1){
    
    
		printf("No find!\n");
	}
	else{
    
    
		printf("%-7s\t%-4s\t%-5s\t%-12s\t%-20s\n", "名字", "年龄", "性别", "电话", "地址");
		printf("%-7s\t%-4d\t%-5s\t%-12s\t%-20s\n",
			ps->data[pos].name,
			ps->data[pos].age,
			ps->data[pos].sex,
			ps->data[pos].tele,
			ps->data[pos].addr);
	}
}

void ModifyContact(Contact *ps){
    
    
	char name[MAX_NAME];
	printf("Input del name:");
	scanf("%s", &name);
	// 1.查找要删除的人在什么位置
	int pos = FindByName(ps, name);
	if (pos == -1){
    
    
		printf("No find!\n");
	}
	// 2.修改
	printf("Input name:");
	scanf("%s", ps->data[pos].name);
	printf("Input age:");
	scanf("%d", &ps->data[pos].age);
	printf("Input sex:");
	scanf("%s", ps->data[pos].sex);
	printf("Input tele:");
	scanf("%s", ps->data[pos].tele);
	printf("Input addr:");
	scanf("%s", ps->data[pos].addr);
	printf("ModifyContact success!\n");
}

//每次仅 1 字节交换规则交换
void swap(char *dst, char *src, int sz){
    
    
	char tmp;
	while (sz--){
    
    
		tmp = *dst;
		*dst = *src;
		*src = tmp;
		++dst, ++src;
	}
}

//自己编写的qsort,通过冒泡实现的排序
void my_qsort(void *base, int num, int width, int(*cmp)(const void *e1, const void *e2)){
    
    
	assert(base && cmp);
	char *tmp = (char *)base;
	int flag = 1;
	for (int i = 0; i < num; ++i){
    
    
		flag = 1;
		for (int j = 0; j < num - 1; ++j){
    
    
			if (cmp(tmp + j*width, tmp + (j + 1)*width) < 0){
    
    
				flag = 0;
				swap(tmp + j*width, tmp + (j + 1)*width, width);
			}
		}
		if (!flag){
    
    
			break;
		}
	}
}

//排序方法
int cmp_PeoInfo_By_Name(const void *e1, const void *e2){
    
    
	return strcmp(((PeoInfo *)e1)->name, ((PeoInfo *)e2)->name);
}

//调用排序函数
void SortContact(Contact *ps){
    
    
	my_qsort(ps->data, ps->sz, sizeof(ps->data[0]), cmp_PeoInfo_By_Name);
	printf("排序成功!\n");
}

//销毁
void DestoryContact(Contact *ps){
    
    
	free(ps->data);
	ps->data = NULL;
	printf("Destory success!\n");
}

//写文件
void SaveContact(Contact *ps){
    
    
	FILE *pfWrite = fopen("contact.dat", "wb");
	if (pfWrite == NULL){
    
    
		printf("Save::%s\n", strerror(errno));
		return;
	}
	for (int i = 0; i < ps->sz; ++i){
    
    
		fwrite(&(ps->data[i]), sizeof(PeoInfo), 1, pfWrite);
	}
	printf("Save success!\n");
	fclose(pfWrite);
	pfWrite = NULL;
}

  1. main.c
#include "contact.h"

void menu(){
    
    
	printf("*************************\n");
	printf("****1.add       2.del****\n");
	printf("****3.search    4.modify*\n");
	printf("****5.show      6.sort***\n");
	printf("****7.Destory   8.Save***\n");
	printf("****0.exit      *********\n");
}

int main(){
    
    
	// 创建通讯录库
	Contact con;
	// 初始化通讯录
	InitContact(&con);
	int input = 0;
	do{
    
    
		menu();
		printf("Select:");
		scanf("%d", &input);
		switch (input){
    
    
		case EXIT:
			printf("Exit success!\n");
			break;
		case ADD:
			AddContact(&con);
			break;
		case DEL:
			DelContact(&con);
			break;
		case SEARCH:
			SearchContact(&con);
			break;
		case MODIFY:
			ModifyContact(&con);
			break;
		case SHOW:
			ShowContact(&con);
			break;
		case SORT:
			SortContact(&con);
			break;
		case DESTORY:
			DestoryContact(&con);
			break;
		case SAVE:
			SaveContact(&con);
			break;
		default:
			printf("Select error!\n");
			break;
		}
	} while (input);
}


3.效果图

在这里插入图片描述

4.如何由基础版简单变为动态内存

  1. 结构体数组变指针

原结构体数组

typedef struct Contact{
    
    
	PeoInfo data[MAX];//结构体数组为基础创建
	int sz;//记录当前通讯录数据量
}Contact;

动态内存结构体指针

typedef struct Contact{
    
    
	PeoInfo *data;//只想通讯录结构体的指针
	int sz;//当前通讯录的数据量
	int capacity;//当前通讯录的最大容量
}Contact;

再动态内存中,再添加一个变量来记录当前通讯录最大容量;因为基础版我们已经手动定义好 MAX为1000 已经是通讯录的最大容量,因此动态内存也需要一个变量来存储它

  1. 动态内存开辟
    选择malloc+realloc的方式是为了减少内存碎片提高内存访问速度但是对于内存的释放有点复杂
void InitContact(Contact *ps){
    
    
	ps->data = (PeoInfo *)malloc(DEFAULT_SZ * sizeof(PeoInfo));//动态内存开辟一段3倍于基础结构体PeoInfo大小的连续内存空间
	if (ps->data == NULL){
    
    //如果内存开辟失败,将在屏幕输出打印
		printf("%s\n", strerror(errno));
		return;
	}
	ps->sz = 0;//初始化的时候当前数据量为0
	ps->capacity = DEFAULT_SZ;//当前通讯录的最大容量
}
  1. 开辟的时机我们需要一个函数来判断
void CheckCapacity(Contact *ps){
    
    
	if (ps->sz >= ps->capacity){
    
    
		// 增容
		PeoInfo *ptr = realloc(ps->data, (ps->capacity + 2)*sizeof(PeoInfo));//realloc将原有数据“保留”增容后初识结构体地址不变。每次扩容仅扩大2个PeoInfo结构体内容大小
		if (ptr != NULL){
    
    
			ps->data = ptr;
			ps->capacity += 2;
			printf("增容成功!\n");
		}else{
    
    
			printf("增容失败!\n");
		};
	}
}

5.动态内存通讯录可以增加一项销毁功能

void DestoryContact(Contact *ps){
    
    
	free(ps->data);
	ps->data = NULL;
	printf("Destory success!\n");
}

这样就可以实现内存的销毁与释放,缺点是:释放后无法继续使用通讯录,因此需要在运行一遍才可以实现继续使用

6.C语言文件操作示例总结

1.什么是文件

磁盘上的文件是文件。 但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件。

1.程序文件

包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀 为.exe)。

2.数据文件

文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内 容的文件。
在以前所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。
其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上。

2.文件名

文件名包含3部分:文件路径+文件名主干+文件后缀
例如:/Users/cxf/CLionProjects/assignment/file.txt
为了方便起见,文件标识常被称为文件名。

3.文件类型

根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
一个数据在内存中是怎么存储的呢?
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输 出,则在磁盘上只占4个字节(VS2013测试)。

测试代码

#include <stdio.h>
int main(){
    
    
    int a = 1000;
      //..表示上一级目录;.表示当前路径
//    FILE *pf = fopen("/Users/cxf/CLionProjects/assignment/file.txt", "wb");//绝对路径
    FILE *pf = fopen("../file.txt", "wb");//相对路径
    fwrite(&a, 4, 1, pf);//数据地址, 数据字节大小, 写入个数, 待写入的文件指针
    fclose(pf);//关闭文件指针
    pf =NULL;//文件指针置为空
    return 0;
}

4.文件缓冲区

ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘 上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐 个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。

5.文件指针

缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及 文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE.
例如,MacOS的GCC编译环境提供的 stdio.h 头文件中有以下的文件类型申明:

typedef	struct __sFILE {
    
    
	unsigned char *_p;	/* current position in (some) buffer */
	int	_r;		/* read space left for getc() */
	int	_w;		/* write space left for putc() */
	short	_flags;		/* flags, below; this FILE is free if 0 */
	short	_file;		/* fileno, if Unix descriptor, else -1 */
	struct	__sbuf _bf;	/* the buffer (at least 1 byte, if !NULL) */
	int	_lbfsize;	/* 0 or -_bf._size, for inline putc */

	/* operations */
	void	*_cookie;	/* cookie passed to io functions */
	int	(* _Nullable _close)(void *);
	int	(* _Nullable _read) (void *, char *, int);
	fpos_t	(* _Nullable _seek) (void *, fpos_t, int);
	int	(* _Nullable _write)(void *, const char *, int);

	/* separate buffer for long sequences of ungetc() */
	struct	__sbuf _ub;	/* ungetc buffer */
	struct __sFILEX *_extra; /* additions to FILE to not break ABI */
	int	_ur;		/* saved _r when _r is counting ungetc data */

	/* tricks to meet minimum requirements even when malloc() fails */
	unsigned char _ubuf[3];	/* guarantee an ungetc() buffer */
	unsigned char _nbuf[1];	/* guarantee a getc() buffer */

	/* separate buffer for fgetln() when line crosses buffer boundary */
	struct	__sbuf _lb;	/* buffer for fgetln() */

	/* Unix stdio files get aligned to block boundaries on fseek() */
	int	_blksize;	/* stat.st_blksize (may be != _bf._size) */
	fpos_t	_offset;	/* current lseek offset (see WARNING) */
} FILE;

不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。
每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节。
一般都是通过一个FILE FILE 下面我们可以创建一个FILE的指针变量:
下面我们可以创建一个FILE
的指针变量:

FILE* pf;//文件指针变量

定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。

6.文件的打开和关闭

文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。
在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的 关系。
ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。

FILE * fopen ( const char * filename, const char * mode );
int fclose ( FILE * stream );

打开方式如下:

文件使用方式 含义 如果指定文件不存在
"r"只读 为了输入数据,打开一个已经存在的文本文件 出错
"w"只写 为了输出数据,打开一个文本文件 建立一个新文件
"a"追加 向文件末尾添加数据 出错
"rb"只读 为了输入数据,打开一个二进制文件 出错
"wb"只写 为了输出数据,打开一个二进制文件 建立一个新文件
"ab"追加 向一个二进制文件末尾添加数据 出错
"r+"读写 为了读写,打开一个文本文件 出错
"w+"读写 为了读写,建立新文件 建立新的文件
"a+"读写 打开一个文件,在文件尾进行读写 建立新文件
"rb+"读写 为了读写,打开一个二进制文件 出错
"wb+"读写 为了读写,建立一个新的二进制文件 建立新文件
"ab+"读写 打开一个二进制文件,在文件末尾进行读写 建立新文件

示例代码:

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

int main(){
    
    
    int a = 1000;
    //..表示上一级目录;.表示当前路径
//    FILE *pf = fopen("/Users/cxf/CLionProjects/assignment/file.txt", "wb");//绝对路径
    FILE *pf = fopen("../file.txt", "r");//相对路径
    if (pf == NULL){
    
    
        printf("%s\n", strerror(errno));//打开失败就会提示报错
    }
    //打开成功
    fwrite(&a, 4, 1, pf);
    //关闭文件
    fclose(pf);
    pf =NULL;
    return 0;
}

7.文件的顺序读写

1.格式化输入出函数

函数定义格式 函数功能 返回值
int printf(char *format, 输出表) 按串format给定输出格式,把输出表各表达式的值,输出到标准输出文件 成功:输出字符数【失败:EOF】
int scanf(char *format, 输入项地址表) 按串format给定输入格式,从标准输入文件读入数据,存入各输入项地址列表指定的存储单元中 成功:输入数据个数【失败:EOF】
int sprintf(char *s, char *format, 输出表) 把输出表中格式化数据输出到(存储)字符串s 成功:输出数据个数【失败:EOF】
int sscanf(char *s, char *format, 输入项地址表) 从字符串s中读取格式化的数据存储到输入地址表中 成功:输入数据个数【失败:EOF】
int fprintf(FILE *pf, char *format, 输出表) 将输出表中数据输出到(存储)pf所指文件中 成功:输出数据个数【失败:负数】
int fscanf(FILE *pf, char *format, 输入项地址表) 按输入地址项格式将所获得的数据存入pf所指的文件中 成功:输入数据个数【失败:EOF】

2.字符(串)输入输出函数

函数定义格式 函数功能 返回值
int getchar() 从标准输入文件读入一个字符 字符ASCII值或EOF
int putchar(char ch) 向标准输出文件输出字符ch 成功:ch【失败:EOF】
char *gets(char *s) 从标准输入 成功:s【失败:EOF】
int puts(char *s) 把字符串s输出到标准输出文件,‘\0’转换为‘\n’输出 成功:换行符【失败:NULL】
int fgetc(FILE *fp) 从fp所指文件中读取一个字符 成功:所取字符【失败:EOF】
int fputc(char ch, FILE *fp) 将字符ch输出到fp所指向的文件 成功:ch【失败:EOF】
char *fgets(char *s, int n, FILE *fp) 从fp所指文件最多度n-1个字符(遇’\n’,^z终止)到字符串s中 成功:s【失败:EOF】
int *fputs(char *s, FILE *fp) 将字符串s输出到fp所指向文件 成功:s的末字符【失败:0】

3.文件操作函数

函数定义格式 函数功能 返回值
FILE *fopen(char *fname, char *mode) 以mode方式打开fname 成功:文件指针【失败:NULL】
int fclose(FILE *fp) 关闭fp所指文件 成功:0【失败:非0】
int feof(FILE *fp) 检查fp所指文件是否结束 是:非0【失败:0】
int fread(T *a, long sizeof(T), unsigned int n, FILE *fp) 从fp所指文件复制n*sizeof(T)个字节的数据,到T类型指针变量a所指内存区域 成功:n【失败:0】
int fwrite(T *a, long sizeof(T), unsigned int n, FILE *fp) 从T类型指针变量a所指初起复制n*sizeof(T)个字节的数据,到fp所指向文件 成功:n【失败:0】
void rewind(FILE *fp) 移动fp所指文件读写位置到文件头
int fseek(FILE *fp, long n, unsigned int posi) 移动fp所指文件读写位置,n为偏移量,posi决定起点位置 成功:0【失败:非0】
long ftell(FILE *fp) 求当前读写位置到文件头的字节数 成功:所求字节数【失败:EOF】
int remove(char *fname) 删除名为fname的文件 成功:0【失败:EOF】
int rename(char *oldfname, char *newfname) 该文件名oldfname为newfname 成功:0【失败:EOF】

说明:fread()和fwrite()中的类型T可以是任意合法定义的类型。

4.文件的随机读写

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

struct S{
    
    
    int n;
    float score;
    char arr[20];
};

//int fwrite(T *a, long sizeof(T), unsigned int n, FILE *fp)
void r_write() {
    
    
    int a = 1000;
    //..表示上一级目录;.表示当前路径
//    FILE *pf = fopen("/Users/cxf/CLionProjects/assignment/file.txt", "wb");//绝对路径
    FILE *pf = fopen("../r_write.txt", "wb");//相对路径
    if (pf == NULL) {
    
    
        printf("%s\n", strerror(errno));//打开失败就会提示报错
    }
    //打开成功
    fwrite(&a, 4, 1, pf);
    //关闭文件
    fclose(pf);
    pf = NULL;
}

//int fread(T *a, long sizeof(T), unsigned int n, FILE *fp)
void r_fread(){
    
    
    struct S tmp= {
    
    0};
    FILE *pf = fopen("../w_fputs.txt", "rb");
    if (pf == NULL){
    
    
        printf("%s\n", strerror(errno));
    }
    //二进制的形式读文件
    fread(&tmp, sizeof(struct S), 1, pf);
    printf("%d %f %s\n", tmp.n, tmp.score, tmp.arr);
    fclose(pf);
    pf = NULL;
}
//int fputc(char ch, FILE *fp)
void w_fputc() {
    
    
    FILE *pf = fopen("../w_fputc.txt", "w");
    if (pf == NULL) {
    
    
        printf("%s\n", strerror(errno));
    }
    /*
     * 写文件:一个一个字符写入
     */
    fputc('z', pf);
    fputc('x', pf);
    fputc('y', pf);
    fputc('\n', pf);
    fputc('c', pf);
    fputc('x', pf);
    fputc('f', pf);
    //关闭文件
    fclose(pf);
    pf = NULL;
}

//int *fputs(char *s, FILE *fp)
void w_fputs(){
    
    
    FILE *pf = fopen("../w_fputs.txt", "w");
    if (pf == NULL){
    
    
        printf("%s\n", strerror(errno));
    }
    /*
     * 写文件:一行一行写到底,多个fputs不会自动换行;如要换行必须在内容中加入"\n"来换行
     */
    fputs("abcd陈雄峰", pf);
    fputs("我曾悲伤的爱过这个世界\n", pf);
    fputs("张晓韵\n", pf);
    fputs("50年后的世界会在地狱的火焰中湮灭\n", pf);
    fclose(pf);
    pf = NULL;
}

//int fgetc(FILE *fp)
void r_fgetc() {
    
    
    FILE *pf = fopen("../w_fputc.txt", "r");
    if (pf == NULL) {
    
    
        printf("%s\n", strerror(errno));
    }
    /*
     * 读文件:一个一个字符读取
     */
    printf("%c", fgetc(pf));
    printf("%c", fgetc(pf));
    printf("%c", fgetc(pf));
    //关闭文件
    fclose(pf);
    pf = NULL;
}

//char *fgets(char *s, int n, FILE *fp)
void r_fgets(){
    
    
    char buf[1024] = {
    
    0};//0有一个换行
    FILE *pf = fopen("../w_fputc.txt", "r");
    if (pf == NULL){
    
    
        printf("%s\n", strerror(errno));
    }
    /*
     * cxf
     * zxy
     * 读文件:一行一行读取
     * 遇到换行就结束,如果要多次读取需要多次fgets
     */
    printf("%s", fgets(buf, 1024, pf));
    printf("%s", fgets(buf, 1024, pf));
    fclose(pf);
    pf = NULL;
}

//int fprintf(char *s, char *format, 输出表)
void w_fprintf(){
    
    
    struct S s = {
    
    100, 3.14f, "陈雄峰"};
    FILE *pf = fopen("../w_fprintf.txt", "w");
    if (pf == NULL){
    
    
        printf("%s\n", strerror(errno));
    }
    //格式化形式写文件
    fprintf(pf, "%d %f %s", s.n, s.score, s.arr);
    fclose(pf);
    pf = NULL;
}

//int fscanf(char *s, char *format, 输入项地址表)
void r_fscanf(){
    
    
    struct S s = {
    
    0};
    FILE *pf = fopen("../w_fprintf.txt", "r");
    if (pf == NULL){
    
    
        printf("%s\n", strerror(errno));
    }
    //格式化的输入数据
    fscanf(pf, "%d %f %s", &(s.n), &(s.score), &(s.arr));
    printf("%d\n", s.n);
    printf("%f\n", s.score);
    printf("%s\n", s.arr);
    fclose(pf);
    pf = NULL;
}

void stdint_stdout() {
    
    
    /*int ch = fgetc(stdin);
    fputc(ch, stdout);

    printf("\n");

    char buf[1024] = {0};
    fgets(buf, 1024, stdin);//从标准输入流读取
    fputs(buf, stdout);//从标准输出流输出

    printf("\n");

    struct S s = {0};
    fscanf(stdin, "%d %f %s", &(s.n), &(s.score), &(s.arr));//格式化获取所有输入流
    fprintf(stdout, "%d %f %s\n", s.n, s.score, s.arr);*///格式化输出所有输出流


    printf("\n");
    char buf1[1024] = {
    
    0};
    struct S tmp = {
    
    0};
    struct S s1 = {
    
    200, 6.28, "陈雄峰陈雄峰"};
    sprintf(buf1, "%d %f %s", s1.n, s1.score, s1.arr);//把格式化的数据换成字符串存储到buf1
    printf("%s\n", s1.arr);
    sscanf(buf1, "%d %f %s", &(tmp.n), &(tmp.score), &(tmp.arr));//从buf1中读取格式化的数据到tmp中
    printf("%s\n", tmp.arr);

    /*
     * scanf/printf:针对标准输入流/输出流的 格式化输入/输出语句
     * fscanf/fprintf:针对所有输入流/输出流的 格式化输入/输出语句
     * sscanf/sprintf:sscanf是从字符串中读取格式化的数据【sprintf是把格式化数据输出成(存储)字符串】
     */
}

//int fseek(FILE *fp, long n, unsigned int posi)
void r_fseek(){
    
    
    FILE *pf = fopen("../w_fputs.txt", "r");
    if (pf == NULL){
    
    
        printf("%s\n", strerror(errno));
    }
    //定位文件指针
    fseek(pf, 1, SEEK_CUR);//文件指针当前位置偏移1字节
//    fseek(pf, -2, SEEK_END);//文件末尾位置向前偏移2字节【倒数第二个字符】
//    fseek(pf, 1, SEEK_SET);//文件起始位置向后偏移1字节

    //读文件
    int ch = fgetc(pf);
    printf("%c\n", ch);
    fclose(pf);
    pf = NULL;
}

//long ftell(FILE *fp)
void r_ftell(){
    
    
    FILE *pf = fopen("../w_fputs.txt", "r");
    if (pf == NULL){
    
    
        printf("%s\n", strerror(errno));
    }
    //定位文件指针
    fseek(pf, 1, SEEK_CUR);//文件指针当前位置偏移1字节
//    fseek(pf, -2, SEEK_END);//文件末尾位置向前偏移2字节【倒数第二个字符】
//    fseek(pf, 1, SEEK_SET);//文件起始位置向后偏移1字节

    //读文件
    int pos = ftell(pf);
    printf("%d\n", pos);
    fclose(pf);
    pf = NULL;
}

void r_rewind(){
    
    
    FILE *pf = fopen("../w_fputs.txt", "r");
    if (pf == NULL){
    
    
        printf("%s\n", strerror(errno));
    }
    //定位文件指针
//    fseek(pf, 1, SEEK_CUR);//文件指针当前位置偏移1字节
    fseek(pf, -2, SEEK_END);//文件末尾位置向前偏移2字节【倒数第二个字符】
//    fseek(pf, 1, SEEK_SET);//文件起始位置向后偏移1字节

    //恢复文件指针
    rewind(pf);
    //读文件
    printf("%c\n", fgetc(pf));
    fclose(pf);
    pf = NULL;
}

5.文件结束的判定

被错误使用的feof
牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。
而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。

  1. 文本文件读取是否结束,判断返回值是否为EOF (fgetc),或者NULL(fgets)
    例如:
  • fgetc判断是否为EOF
  • fgets判断返回值是否为NULL
  1. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
    例如: fread判断返回值是否小于实际要读的个数。
    正确的使用:

文本文件举例:

    FILE *pf = fopen("../w_fputs.txt", "r");
    if (pf == NULL) {
    
    
        perror("Open w_fputs.txt faild: ");
    }
    //fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
    int ch;//int非char,要处理EOF
    while ((ch = fgetc(pf)) != EOF) {
    
    //标准I/O读取文件循环
        putchar(ch);
    }
    //判断什么原因结束的
    if (ferror(pf)) {
    
    
        puts("I/O error when reading!");
    } else if (feof(pf)) {
    
    
        puts("end of file reached successfuly!");
    }
    fclose(pf);
    pf = NULL;

二进制文件举例:

    enum {
    
    
        size = 5
    };
    double a[size] = {
    
    1.1, 2.2, 3.3, 4.4, 5.5}, b;
    size_t ret_code = 0;
    FILE *pf = fopen("../bin_end.bin", "wb");
    if (pf == NULL) {
    
    
        perror("Open bin_end.bin faild: ");
    }
    fwrite(a, sizeof(a), 1, pf);
    fclose(pf);

    pf = fopen("../bin_end.bin", "rb");
    if (pf == NULL) {
    
    
        perror("Open bin_end.bin faild: ");
    }
    while ((ret_code = fread(&b, sizeof(a), 1, pf)) == 1) {
    
    
        printf("%lf\n", b);
    }
    //判断什么原因结束的
    if (ferror(pf)) {
    
    
        puts("I/O error when reading!");
    } else if (feof(pf)) {
    
    
        puts("end of file reached successfuly!");
    }
    fclose(pf);
    pf = NULL;

7. 素数问题

void Prime() {
    
    
    for (int i = 0; i <= 100; ++i) {
    
    
        int j = 2;
        for (; j < i; ++j) {
    
    
            if (i % j == 0) {
    
    
                break;
            }
        }
        if (j == i && i != 1) {
    
    
            printf("%d ", i);
        }
    }

    printf("\n******************\n");

    for (int i = 0; i <= 100; ++i) {
    
    
        int j = 2;
        for (; j <= i / 2; ++j) {
    
    
            if (i % j == 0) {
    
    
                break;
            }
        }
        if (j > i / 2 && i != 0 && i != 1) {
    
    
            printf("%d ", i);
        }
    }

    printf("\n******************\n");

    for (int i = 0; i <= 100; ++i) {
    
    
        int j = 2;
        for (; j <= (int)sqrt(i); ++j) {
    
    
            if (i % j == 0) {
    
    
                break;
            }
        }
        if (j > (int)sqrt(i) && i != 0 && i != 1) {
    
    
            printf("%d ", i);
        }
    }

    printf("\n******************\n");
    
    int a[100];
    //填充数据
    for (int i = 0; i <= 100; ++i) {
    
    
        a[i] = i + 1;
    }
    //1不是素数,只需要将1划掉-->使用0进行填充
    a[0] = 0;
    for (int i = 1; i < 100; ++i) {
    
    
        if (a[i] == 0) {
    
    
            continue;
        }
        //现在需要使用a[i]%之后的所有数据
        for (int j = i + 1; j < 100; ++j) {
    
    
            if (a[j] != 0 && a[j] % a[i] == 0) {
    
    
                a[j] = 0;
            }
        }
    }
    for (int i = 0; i < 100; ++i) {
    
    
        if (a[i] != 0) {
    
    
            printf("%d ", a[i]);
        }
    }
}

8. 乘法口诀表

void table(){
    
    
    for (int i = 1; i <= 9; ++i) {
    
    
        for (int j = 1; j <= i; ++j) {
    
    
            printf("%d*%d=%2d ", j,i,j*i);
        }
        printf("\n");
    }
}

9. 平闰年

void year(){
    
    
    int count = 1;//计数器
    int year_star = 1900;
    int year_end = 2080;
    while (year_star <= year_end) {
    
    
        if ((year_star % 4 == 0 && year_star % 100 != 0) || (year_star % 100 == 0 && year_star % 400 == 0)) {
    
    
            printf("%d年是闰年\n", year_star);
            count++;
        }
        year_star++;
    }
    printf("1900年到2080年间共有%d个闰年\n", count);
}

10. 数据交换(无临时变量)

void swap(){
    
    
    int i = 10, j = 20;
    /*
     * 0&0=0;0&1=0;1&0=0;1&1=1【两个同时为1,结果为1,否则为0】
     * 0|0=0;0|1=1;1|0=1;1|1=1【参加运算的两个对象,一个为1,其值为1】
     * 0^0=0;0^1=1;1^0=1;1^1=0【参加运算的两个对象,如果两个位为“异”(值不同),则该位结果为1,否则为0。】
     * 01010:10
     * 10100:20
     * 11110>>2+4+8+16
     */
    printf("交换前i:%d,j:%d\n", i, j);
    //i^=j^=i^=j:这样的异或一行代码即可解决交换问题,下边是它的展开式
    i = i ^ j;//i ^ i = 0
    j = i ^ j;//j = (i ^ j) ^ j = i ^ 0 = i
    i = i ^ j;//i = (i ^ j) ^ i = j
    printf("交换后i:%d,j:%d", i, j);
}

11. 10个数中在最大值(数组便利冒泡方法)

void max() {
    
    
    int i, max, arr[10] = {
    
    1, 3, 5, 7, 9, 2, 4, 6, 8, 10};
    printf("请输入10个数字【输入一次按一次回车!】:");
    for (i = 1; i <= sizeof(arr) / sizeof(arr[0]); ++i) {
    
    
        scanf("%d\t", &arr[i]);
    }
    max = arr[0];
    for (int j = 0; j <= sizeof(arr) / sizeof(arr[0]); ++j) {
    
    
        if (max < arr[j]) {
    
    
            max = arr[j];
        }
    }
    printf("最大值是:%d\n", max);
}

12. 3个数大小顺序输出

void swap(int *m, int *n) {
    
    
    int t;
    t = *n;
    *n = *m;
    *m = t;
}
void threeSort() {
    
    
    int a, b, c;
    printf("请输入三个数:\n");
    scanf("%d%d%d", &a, &b, &c);
    if (a < b) {
    
    
        swap(&a, &b);
    }
    if (a < c) {
    
    
        swap(&a, &c);
    }
    if (b < c) {
    
    
        swap(&b, &c);
    }
    printf("三个数从大到小为:%d,%d,%d\n", a, b, c);
}

13.3个数最大值

#define Max(a, b) (a > b ? a : b)
void threeMax() {
    
    
    int a, b, c;
    printf("输入三个数字:\t");
    scanf("%d%d%d", &a, &b, &c);
    printf("max = %d\n", Max(a, b) > c ? Max(a, b) : c);
}

14.最大公因数,最小公倍数

void multiple_factor(){
    
    
    int m, n;
    printf("请输入两个数:");
    scanf("%d%d", &m, &n);
    for (int i = m; i > 0; ++i) {
    
    
        if (i % m ==0 && i % n ==0){
    
    
            printf("%d和%d最小公倍数是:%d,", m, n, i);
            break;//找到就停止,则就是最小公倍数
        }
    }
    for (int i = n; i > 0; --i) {
    
    
        if (m % i ==0 && n % i ==0){
    
    
            printf("最大公约数是:%d\n", i);
            break;//找到就停止,则就是最大公约数
        }
    }

    printf("***********\n");

    int tmp, m1=m, n1 = n;
    while(m%n!=0){
    
    
        tmp = m%n;
        m = n;
        n = tmp;
    }
    printf("%d和%d最小公倍数是:%d,最大公约数是:%d\n", m1, n1, m1*n1/tmp, tmp);
}

15.数组互换

void swap2() {
    
    
    int arr1[5] = {
    
    1, 3, 5, 7, 9};
    int arr2[5] = {
    
    2, 4, 6, 8, 10};
    for (int i = 0; i < sizeof(arr1) / sizeof(arr1[0]); ++i) {
    
    
//        printf("交换前:arr1[%d] = %d, arr2[%d] = %d\n", i, arr1[i], i, arr2[i]);
        arr1[i] = arr1[i] ^ arr2[i];
        arr2[i] = arr1[i] ^ arr2[i];
        arr1[i] = arr1[i] ^ arr2[i];
//        printf("交换后:arr1[%d] = %d, arr2[%d] = %d\n", i, arr1[i], i, arr2[i]);
    }
    printf("arr1: ");
    for (int i = 0; i < sizeof(arr1) / sizeof(arr1[0]); ++i) {
    
    
        printf("%d ", arr1[i]);
    }
    printf("\n");
    printf("arr2: ");
    for (int i = 0; i < sizeof(arr1) / sizeof(arr1[0]); ++i) {
    
    
        printf("%d ", arr2[i]);
    }
}

16.e=1-1/2!+1/3!-1/4!..1/n!使用此公式求e近似值,要求累加所有绝对值不小于1e-6

void t3(){
    
    
    int i=1, flag=1;
    float n=1, sum=0;
    while(fabs(n)>1e-6){
    
    
        n = 1.0*flag/fun(i++);
        flag *= -1;
        sum += n;
    }
    printf("%lf\n", sum);
}

17.求1~100带有数字9的个数

void count() {
    
    
    int count = 0;
    for (int i = 0; i <= 100; ++i) {
    
    
        /*if ((i % 10) == 9 || (i / 9 == 9)) {
            printf("%d\n", i);
            count++;
        }*/
        (i % 10 == 9) || (i / 9 == 9) ? count++ : 0;
    }
    printf("共有%d个带有9的数字\n", count);
}

18.扫雷游戏

  1. 编写思维
    可以多文件也可以写在一个mian函数中
    这里选择了整合在一起,但在实际编写中,多文件更好
    扫雷需要两个棋盘,通过memset函数完成对字符数组的初始化,show_board和mine_board两个二位数组。show_board数组是为了打印屏幕,mine_board是为了实现扫雷的逻辑判断
    需要用到的函数有:
  • main:主函数
  • Menu:游戏选项菜单;
  • Game:
    1⃣️判断玩家输入坐标是否越界【show_board二维数组仅仅去除最外围的一圈,保留内部,后期不用考虑判断问题和玩家输入坐标值差1问题】
    2⃣️判断玩家输入的坐标值是否被排除过,防止重复排雷
    3⃣️判断玩家是否被炸死
    4⃣️顺便将玩家指定的坐标周四周八个点的雷数量值覆盖玩家输入的制定坐标轴的值
    5⃣️判断输赢
  • SetMines:通过随机值在mine_board中布置一定数量的地雷
  • GetMines:棋盘上通过二维数组show_board模拟出的坐标轴,用户输入x,y即可获取指定坐标并计算坐标(x,y)周围8个格子内部的地雷数量
  • ShowBoard:主要展现show_board二维数组的内容打印在屏幕,通过for循环打印第一行数字,再通过嵌套循环打印除show_board第一行所有元素之外的全部元素
  • ShowLine:展示二维数组show_board打印一行之后的换行效果
  1. 代码
#ifndef __GAME_H__
#define __GAME_H__

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

#define ROW 12//二维数组的行
#define COL 12//二维数组的列
#define NUMS 20//指定的地雷数量

#endif

//游戏菜单
void Menu() {
    
    
    printf("#################\n");
    printf("##1.Play 2.Exit##\n");
    printf("#################\n");
    printf("请选择:");
}
//随机埋地雷
void SetMines(char mine_board[ROW][COL]) {
    
    
    int count = NUMS;
    while (count) {
    
    
        int x = rand() % 10 + 1;//为了取得1~10之间的整数,防止在12个单位的二维数组中越界
        int y = rand() % 10 + 1;
        if (mine_board[x][y] == '0') {
    
    
            mine_board[x][y] = '1';
            count--;
        }
    }
}
//为了展示二维数组show_board打印一行之后的换行效果
void ShowLine(nums) {
    
    
    for (int i = 0; i < nums; ++i) {
    
    
        printf("-");
    }
    printf("\n");
}
//展现二维数组show_board的内容,其中|,' ',*,和数字之间的对齐要注意有ROW和COL决定,这里仅仅是配12行12列
void ShowBoard(char show_board[ROW][COL]) {
    
    
    printf("   ");
    for (int i = 1; i < ROW-1; ++i) {
    
    
        printf("%2d |", i);
    }
    printf("\n");
    ShowLine(3 * COL + 7);
    for (int i = 1; i < ROW-1; ++i) {
    
    
        printf("%2d|", i);
        for (int j = 1; j < COL-1; ++j) {
    
    
            printf(" %c |", show_board[i][j]);
        }
        printf("\n");
        ShowLine(3 * COL + 7);
    }
}
//获取玩家输入(x,y)之后周围8个单位中地雷'1'的数量【字符'1'减去字符'0'等于1】
int GetMines(char mine[ROW][COL], int x, int y) {
    
    
    return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] + \
    mine[x][y - 1] + mine[x][y + 1] + \
    mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0';
}
//整个游戏的运转流程
void Game() {
    
    
	//以时间为种子,进行无限次数随机
    srand((unsigned int) time(NULL));
    char show_board[ROW][COL];
    char mine_board[ROW][COL];
    //初始化打印屏幕给用户看的棋盘
    memset(show_board, '*', sizeof(show_board));
    //初始化实现逻辑判断的棋盘
    memset(mine_board, '0', sizeof(mine_board));
    SetMines(mine_board);
    int count = (ROW - 2) * (COL - 2) - NUMS;
    int x = 0;
    int y = 0;
    do {
    
    
        ShowBoard(show_board);
        printf("请输入你的位置:");
        scanf("%d %d", &x, &y);
        if (x < 1 || x > 10 || y < 1 || y > 10) {
    
    
            printf("输入越界\n");
            continue;
        }
        if (show_board[x][y] != '*') {
    
    
            printf("对不起,该位置已经被排除\n");
            continue;
        }
        if (mine_board[x][y] == '1') {
    
    
            break;
        }
        int num = GetMines(mine_board, x, y);
        show_board[x][y] = num + '0';
        count--;
    } while (count);
    //意外break退出游戏判断count>0就代表踩中地雷
    if (count > 0) {
    
    
        printf("你猜到了雷\n");
    } else {
    
    
        printf("你赢了\n");
    }
    //游戏无论输赢都会给玩家展现雷区的分布图
    printf("以下是雷区分布图:\n");
    ShowBoard(mine_board);
}

int main() {
    
    
    int quit = 0;
    int select = 0;
    while (!quit) {
    
    
        Menu();
        scanf("%d", &select);
        switch (select) {
    
    
            case 1:
                Game();
                printf("再来一盘\n");
                break;
            case 2:
                printf("推出\n");
                quit = 1;
                break;
            default:
                printf("输入有误\n");
                break;
        }
    }
    return 0;
}

19.三字旗

  1. 逻辑思维
  • main:主函数
  • Menu:游戏菜单选项
  • Game:游戏的开始,内部调用ComputerMove,Judg和PlayerMove函数并在Game函数内判断输,平局,赢三种结果
  • ComputerMove:电脑随机下棋
  • Judge:判断玩家不同棋子摆放状态,第一个完成三个相同棋子的玩家返回对应玩家棋子
  • PlayerMove:玩家下棋
  1. 代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#define ROW 3//二维数组board的行
#define COL 3//二维数组board的列
#define NEXT 'N'//Judge函数判断正在下
#define P_COLOR 'X'//Judge判断玩家赢
#define C_COLOR '0'//Judge判断电脑赢
#define DRAW 'D'//Judge判断和棋

//一个简陋的菜单选项
void Menu(){
    
    
    printf("##############\n");
    printf("##1.Play  2.Exit##\n");
    printf("##############\n");
    printf("请选择:");
}
//展现二维数组board内容打印在屏幕上
void ShowBoard(char board[][COL], int row, int col){
    
    
    printf(" |1|2|3|\n");
    for (int i=0; i<row; ++i){
    
    
        printf("%d|", i+1);
        for (int j=0; j<col; ++j){
    
    
            printf("%c|", board[i][j]);
        }
        printf("\n--------\n");
    }
}
//玩家下
void PlayerMove(char board[][COL], int row, int col){
    
    
    int x=0;
    int y=0;
    while(1){
    
    
        printf("请输入坐标:");
        scanf("%d %d", &x, &y);
        //因为是三子棋,所以最大范围是3【包含3】;由于第一列有数字当替,所以最小范围是1【包含1】
        if (x<1 || x>3 || y<1 || y>3){
    
    
            printf("坐标越界\n");
            continue;
        }else if (board[x-1][y-1] !=' '){
    
    
            printf("坐标已经被占用\n");
            continue;
        }else{
    
    
            board[x-1][y-1]=P_COLOR;
            break;
        }
    }
}
//电脑在1~3之类随机下棋
void ComputerMove(char board[][COL], int row, int col){
    
    
    while(1){
    
    
        int x=rand()%3+1;
        int y=rand()%3+1;
        if(board[x][y] == ' '){
    
    
            board[x][y] = '0';
            break;
        }
    }
}
//判断下棋状态
char Judge(char board[][COL], int row, int col){
    
    
    //行
    for (int i=0; i<ROW; ++i){
    
    
        if (board[i][0] == board[i][1] && board[i][1]==board[i][2] && board[i][0] != ' ') return board[i][0];
    }

    //列
    for (int i=0; i<COL; ++i){
    
    
        if (board[0][i] == board[1][i] && board[1][i]==board[2][i] && board[0][i] != ' ') return board[0][i];
    }

    //撇
    if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2] != ' ') return board[0][2];

    //捺
    if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[0][0] != ' ') return board[0][0];

    //正在下
    for (int i=0; i<ROW; ++i){
    
    
        for(int j=0; j<COL; ++j){
    
    
            if (board[i][j] == ' ') return NEXT;
        }
    }

    //和棋
    return DRAW;
}

void Game(){
    
    
    srand((unsigned int)time(NULL));
    char board[ROW][COL];
    memset(board, ' ', sizeof(board));
    char result='X';
    do{
    
    
        ShowBoard(board, ROW, COL);
        PlayerMove(board, ROW, COL);
        result=Judge(board, ROW, COL);
        if (result != NEXT){
    
    
            break;
        }
        ComputerMove(board, ROW, COL);
        result=Judge(board, ROW, COL);
        if (result != NEXT){
    
    
            break;
        }
    }while(1);
    if (result == P_COLOR){
    
    
        printf("你赢了\n");
        ShowBoard(board, ROW, COL);
    }else if (result == C_COLOR){
    
    
        printf("电脑赢了\n");
        ShowBoard(board, ROW, COL);
    }else{
    
    
        printf("平局\n");
        ShowBoard(board, ROW, COL);
    }
}

int main(){
    
    
    int quit=0;
    int select=0;
    while(!quit){
    
    
        Menu();
        scanf("%d", &select);
        switch(select){
    
    
            case 1:
                Game();
                printf("在玩一盘\n");
                break;
            case 2:
                quit=1;
                printf("推出\n");
                break;
            default:
                printf("输入有误\n");
                break;
        }
    }
    return 0;
}

20.C语言计算二进制中1的个数三种不同境界

void count() {
    
    
    /*
     * 1.模2除2思想:【这个思想的缺陷是无法针对负数使用】可以从水仙花数中求个位,十位,百位中获得启发:通过模10除10得到对应的每一位十进制中数字,对于二进制通过模2除2就可以得到对应的每一位二进制数字
    */
    int num = 7;
    int count = 0;
    while (num) {
    
    
        if (num % 2 == 1) {
    
    
            count++;
        }
        num /= 2;
    }
    printf("%d\n", count);

    /*
     * 2. ## 右移思想对于电脑而言,举例32为系统,解决传统的模2除2思想缺点
    */
    int num2 = -1;
    int count2 = 0;
    for (int i=1; i<=32; i++){
    
    
        if ((num2 >> i)&1 == 1){
    
    
            count2++;
        }
    }
    printf("%d\n", count2);

    /*
     * 0111--7【num】的补码
     * 0111 num
     * 0110 num-1
     * 0110 num & (num-1)
     * 0110 num
     * 0101 num -1
     * 0100 num & (num-1)
     * 0100 num
     * 0010 num-1
     * 0000 num &(num-1)
     * 一共计算了三次,最后一次结果为0,while循环终止
    */
    int num3 = -1;
    int count3 = 0;
    while(num3){
    
    
        num3 &= (num3-1);
        count3++;
    }
    printf("%d\n", count3);
}

21. C语言求两个数二进制中不同位的个数

  1. 解题思路
    利用上一题:14.C语言计算二进制中1的个数三种不同境界
    将n与m进行异或运算,得到新的数tmp,tmp中为1的就代表n与m不相同的为,tmp再与tmp-1进行按位与运算求得1的个数就代表二进制中不同为的个数
void count(){
    
    
	int n = 1999;
	int m = 2299;
	int count = 0;
	int tmp = n ^ m;
	while (tmp){
    
    
		tmp &= (tmp-1);
		count++;
	}
	printf("%d\n", count);
}

22.C语言打印整数二进制的奇数位和偶数位

  1. 解题思想
    平台:32位系统
    利用右移系统的奇位数再与1进行按位与运算,位都为1则为1。
    注意偶数位:因为对于有符号数来说最高位是符号位,因此不能取第32位只能退而求其次取第30位
void Print(){
    
    
	int num = 7;
	printf("奇数:");
	for (int i=31; i>=1; i-=2){
    
    
		printf("%d ", (num>>i)&1);
	}
	printf("偶数:");
	for (int i=30; i>=0; i-=2){
    
    
		printf("%d ", (num>>i)&1);
	}
}

23.猴子吃桃:猴子每天吃桃子的一半又多吃一个。到第n天想吃的时候,只有1个桃子。求一共有多少桃子【理清关系:前一天桃子数=2*(后一天桃子数+1)】

void peach() {
    
    
    int total = 1, n;
    scanf("%d", &n);
    for (int i = 2; i <= n; ++i) {
    
    //注意循环条件
        total = 2 * (total + 1);
    }
    printf("%d\n", total);
}

24.兔子繁衍:一对兔子,从出生3个月开始每个月都生一对兔子。小兔子3个月后每个月又生一对兔子。假设兔子都不死,问第至少繁衍到第几个月,兔子总数才能达到n对。手动输入兔子个数,自动输出所需月数【把兔子数量列出来,会发现和斐波那契数列一样】

void rabbit() {
    
    
    int n, day = 1;
    scanf("%d", &n);
    int r1 = 1, r2 = 1, r3 = 1;
    while (r3 <= n) {
    
    
        r3 = r1 + r2;
        r1 = r2;
        r2 = r3;
        ++day;
    }
    printf("%d\n", day);
}

25.输入两个数正整数m和n,输出m~n之间的所有的斐波那契数,要求定义fab函数,功能是返回第n项的斐波那契数列入fab(7)返回值是21【1 1 2 3 5 8 13 21…】

int fab(int n) {
    
    
    if (n == 1 || n == 2) {
    
    
        return 1;
    } else {
    
    
        int r1 = 1, r2 = 1, r3 = 1;
        for (int i = 3; i <= n; ++i) {
    
    
            r3 = r1 + r2;
            r1 = r2;
            r2 = r3;
        }
        return r3;
    }
}

void Print() {
    
    
    int m, n;
    scanf("%d %d", &m, &n);
    for (int i = m; i <= n; ++i) {
    
    
        printf("%d ", fab(i));
    }
}

26.打印菱形

void Print() {
    
    
    int col = 3;
//    printf("Input col:");
//    scanf("%d", &col);
    for (int i = 0; i < col; ++i) {
    
    
        for (int j = 0; j < col - i - 1; ++j) {
    
    
            printf(" ");
        }
        for (int j = 0; j < 2 * i + 1; ++j) {
    
    
            printf("*");
        }
        printf("\n");
    }
    for (int i = 0; i < col - 1; ++i) {
    
    
        for (int j = 0; j < i + 1; ++j) {
    
    
            printf(" ");
        }
        for (int j = 0; j < 2 * (col - i - 1) - 1; ++j) {
    
    
            printf("*");
        }
        printf("\n");
    }
}

27.杨辉三角问题

void yhsj(){
    
    
    int a[10][10];
    for (int i = 0; i < 10; ++i) {
    
    
        for (int j = 0; j <= i; ++j) {
    
    
            if (j==0 || i==j){
    
    
                a[i][j] = 1;
            } else{
    
    
                a[i][j] = a[i-1][j]+a[i-1][j-1];
            }
        }
    }
    for (int i = 0; i < 10; ++i) {
    
    
        for (int j = 0; j <= i; ++j) {
    
    
            printf("%d ", a[i][j]);
        }
        printf("\n");
    }
}

28.不考虑优先级的计算器【10-10*2=0】

void cal() {
    
    
    int operan1, operan2, res;
    char c;
    scanf("%d", &operan1);
    c = getchar();
    while (c != '=') {
    
    
        scanf("%d", &operan2);
        switch (c) {
    
    
            case '+':
                res = operan1 + operan2;
                break;
            case '-':
                res = operan1 - operan2;
                break;
            case '*':
                res = operan1 * operan2;
                break;
            case '/':
                res = operan1 / operan2;
                break;
            default:
                res = 0;
                break;
        }
        operan1 = res;
        c = getchar();
    }
    printf("%d\n", res);
}

29.对角线元素之和

void sum() {
    
    
    int a[3][3] = {
    
    {
    
    1, 2, 3},
                   {
    
    4, 5, 6},
                   {
    
    7, 8, 9}},
                   sum_r = 0, sum_l = 0, col = 3;
    //左上角与右下角
    for (int i = 0; i < col; ++i) {
    
    
        sum_l += a[i][i];
    }
    //左下角与右上角
    for (int i = 0, j = col - 1; i < col; ++i, --j) {
    
    
        sum_r += a[i][j];
    }
    printf("左上角与右下角: %d, 左下角与右上角: %d\n", sum_l, sum_r);
}

30.上三角矩阵行列互换

#include <time.h>
#include <stdlib.h>

#define cow 5

void Print(int (*a)[cow]) {
    
    
    for (int i = 0; i < cow; ++i) {
    
    
        for (int j = 0; j < cow; ++j) {
    
    
            printf("%d ", (*(a + i))[j]);
        }
        printf("\n");
    }
}

void swap() {
    
    
    srand((unsigned int) time(NULL));
    int a[cow][cow] = {
    
    0};
    for (int i = 0; i < cow; ++i) {
    
    
        for (int j = 0; j < cow; ++j) {
    
    
            a[i][j] = rand() % 11;
        }
    }
//    int a[cow][cow] = {
    
    {1,2,3},{4,5,6},{7,8,9}};
    Print(a);
    for (int i = 0; i < cow; ++i) {
    
    
        for (int j = i; j < cow; ++j) {
    
    
//            if (i<=j){
    
    
//                a[i][j] ^= a[j][i] ^= a[i][j] ^= a[j][i];
            int tmp = a[i][j];
            a[i][j] = a[j][i];
            a[j][i] = tmp;
//            }
        }
    }
    printf("\n");
    Print(a);
}

31.将输入的字符,除16进制外其余全部过滤后转为10进制输出

void sixteen_to_ten() {
    
    
    long num = 0;
    char a[80], b[80];
    int index_a = 0, index_b = 0;
    while ((a[index_a++] = getchar()) != '#') {
    
    
        if (a[index_a - 1] == '-' || 'A' <= a[index_a - 1] && a[index_a - 1] <= 'F' ||
            'a' <= a[index_a - 1] && a[index_a - 1] <= 'f' || '0' <= a[index_a - 1] && a[index_a - 1] <= '9') {
    
    
            b[index_b++] = a[index_a - 1];
        }
    }
    for (int i = 0; i < index_b; ++i) {
    
    
        if ('A' <= b[i] && b[i] <= 'F') {
    
    
            num = num * 16 + b[i] - 'A' + 10;
        } else if ('a' <= b[i] && b[i] <= 'f') {
    
    
            num = num * 16 + b[i] - 'a' + 10;
        } else if ('0' <= b[i] && b[i] <= '9') {
    
    
            num = num * 16 + b[i] - '0';
        }
    }
    if (b[0] == '-') {
    
    
        printf("%ld\n", -num);
    } else {
    
    
        printf("%ld\n", num);
    }
}

32.输出0~10000范围内因子之和等于本身的数[6=1+2+3]

void t21() {
    
    
    for (int i = 2; i <= 10000; ++i) {
    
    
        int sum = 0;
        for (int j = 1; j <= i / 2; ++j) {
    
    
            if (i % j == 0) {
    
    
                sum += j;
            }
        }
        if (sum == i) {
    
    
            printf("%d: ", i);
            for (int j = 1; j <= i / 2; ++j) {
    
    
                if (i % j == 0) {
    
    
                    printf("%d ", j);
                }
            }
            printf("\n");
        }
    }
}

33.数组逆序

void reverse() {
    
    
    int a[10] = {
    
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, r = 0, l = 9;
    while (r < l) {
    
    
        a[r] ^= a[l] ^= a[r] ^= a[l];
        ++r, --l;
    }
    for (int i = 0; i < 10; ++i) {
    
    
        printf("%d ", a[i]);
    }
}

34.一个有序数组插入元素后依然有序

void insert() {
    
    
    int a[10] = {
    
    0, 1, 2, 3, 4, 6, 7, 8, 9}, sz = 8;
    int key = 5;
//    printf("Input key:");
//    scanf("%d", &key);
    //从后往前找
    while (sz >= 0 && key < a[sz]) {
    
    
        a[sz + 1] = a[sz];
        --sz;
    }
    a[sz + 1] = key;
    for (int i = 0; i < 10; ++i) {
    
    
        printf("%d ", a[i]);
    }
}

35.选择排序

void selectSort() {
    
    
    int a[10] = {
    
    2, 4, 6, 8, 10, 1, 3, 5, 7, 9}, index, sz = sizeof(a) / sizeof(a[0]);
    for (int i = 0; i < sz - 1; ++i) {
    
    
        index = i;
        for (int j = i + 1; j < sz; ++j) {
    
    
            if (a[index] > a[j]) {
    
    
                a[index] ^= a[j] ^= a[index] ^= a[j];
            }
        }
    }
    for (int i = 0; i < sz; ++i) {
    
    
        printf("%d ", a[i]);
    }
}

36.数组循环移动指定位数

void t17(int a[], int n, int sz) {
    
    
    int tmp[80];
    for (int i = 0; i < sz; ++i) {
    
    
        if (i + n < sz) {
    
    
            tmp[i + n] = a[i];
        } else {
    
    
            tmp[(i + n) % sz] = a[i];
        }
    }
    for (int i = 0; i < sz; ++i) {
    
    
        a[i] = tmp[i];
        printf("%d ", a[i]);
    }
}

37.输入 2 个正整数 a 和 n,求 a+aa+aaa+aa…a(n 个 a)之和

void add_same() {
    
    
    int a, n, sum = 0, tmp = 0;
    printf("Input a n:");
    scanf("%d %d", &a, &n);
    for (int i = 0; i < n; ++i) {
    
    
        sum += (tmp = tmp * 10 + a);
    }
    printf("%d\n", sum);
}

38.将一笔零钱(大于 8 分,小于 1 元,精确到分)换成 5 分、2 分和 1 分的硬币,每种硬币至少有一枚。输入金额,问有哪几种换法?针对每一种换法, 输出各种面额硬币的数量和硬币的总数量

void charge() {
    
    
    int money, num_1, num_2, num_5, sum;
    printf("Input money:");
    scanf("%d", &money);
    for (num_5 = money / 5; num_5 > 0; --num_5) {
    
    
        for (num_2 = money / 2; num_2 > 0; --num_2) {
    
    
            for (num_1 = money; num_1 > 0; --num_1) {
    
    
                if (5 * num_5 + 2 * num_2 + num_1 == money) {
    
    
                    printf("5:%d, 2:%d, 1:%d\n", num_5, num_2, num_1);
                }
            }
        }
    }
}

39.输入一个正整数 n(3≤n≤7)输出所有 n 位水仙花数。水仙 花数是指一个 n 位正整数,它各位数字的 n 次幂之和等于它本身

void water_flower_all() {
    
    
    int n, tmp;
    printf("Input m:");
    scanf("%d", &n);
    for (int i = (int) pow(10, n - 1); i < pow(10, n); ++i) {
    
    
        int count = 0, sum = 0;
        tmp = i;
        while (tmp) {
    
    
            tmp /= 10;
            ++count;
        }
        tmp = i;
        for (int j = 0; j < count; ++j) {
    
    
            sum += (int) pow(tmp % 10, count);
            tmp /= 10;
        }
        if (sum == i) {
    
    
            printf("%d ", i);
        }
    }
}

40.输入一个小写字母,将字母循环后移5个单位后输出 a->f;v->a

void move() {
    
    
    char c;
    printf("Input c:");
    scanf("%c", &c);
    if (c >= 'a' && c <= 'u') {
    
    //u vwxyz: 要包含u即可[<='u'或者<v]
        c += 5;
    } else if (c >= 'v' && c <= 'z') {
    
    
        c -= 21;//
    }
    putchar(c);
}

41.十进制数转换为二进制数,并将二进制数输出

void Dev2Bin() {
    
    
    int bin[32] = {
    
    0};
    int i, num;
    printf("Input num:");
    scanf("%d", &num);
    for (i = 0; num != 0; ++i) {
    
    
        bin[i] = num % 2;
        num /= 2;
    }
    for (; i != 0; --i) {
    
    
        printf("%d", bin[i - 1]);
    }
}

43.序列求和(1-1/4+1/7-1/10+1/13-1/16+…):输入一个正实数eps,精确到最后一项的绝对值小于 eps(保留 6 位小数)

#include <math.h>

void t_4__4_3() {
    
    
    int deno = 1, flag = 1;
    float sum = 0, item = 1, eps;
    printf("Input eps:");
    scanf("%f", &eps);
    while (fabs(item) >= eps) {
    
    
        item = 1.0 * flag / deno;
        sum += item;
        deno += 3, flag *= -1;
    }
    printf("%f\n", sum);
}

44.输入1个正整数n,计算1 + 1/1!+1/ 2!+1/ 3!+…+1/n!(保留2位小数),要求使用嵌套循环

void t_4_4_7() {
    
    
    int n, item;
    float sum = 1;
    printf("Input n:");
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) {
    
    
        item = 1;
        for (int j = 1; j <= i; ++j) {
    
    
            item *= j;
        }
        sum += 1.0 / item;
    }
    printf("%0.2f\n", sum);
}

45.乒乓球对战:A说他不和X比,C说他不和X、Z比

void ball() {
    
    
    for (int A = 'X'; A <= 'Z'; ++A) {
    
    
        for (int B = 'X'; B <= 'Z'; ++B) {
    
    
            for (int C = 'X'; C <= 'Z'; ++C) {
    
    
                if (A != B && A != C && B != C && A != 'X' && C != 'X' && C != 'Z') {
    
    
                    printf("A:%c B:%c C:%c\n", A, B, C);
                }
            }
        }
    }
}

46.二分法求方程:2x3-4*x2+3x-6=0

void func() {
    
    
    double tmp = 10, l = -10, r = 10, mid;
    while (fabs(tmp) > 1e-5) {
    
    
        mid = (r + l) / 2;
        tmp = 2 * mid * mid * mid - 4 * mid * mid + 3 * mid - 6;
        if (tmp > 0) {
    
    
            r = mid;
        } else if (tmp < 0) {
    
    
            l = mid;
        }
    }
    printf("%lf\n", mid);
}

47.高度100米,反弹一半距离:小球落地求路程和距离问题

void ball_down() {
    
    
    double hight = 100.0, distance = 0.0;
    for (int i = 0; i < 1; ++i) {
    
    
        distance += 3 * (hight /= 2);
    }
    distance -= hight;
    printf("路程:%lf, 距离:%lf\n", distance, hight);
}

48.输出实数的整数和小数部分

void int_float(float x, int *intpart, float *fracpart) {
    
    
    *intpart = (int) x;
    *fracpart = x - *intpart;
}

49.冒泡排序

void BubbleSort(int arr[], int sz) {
    
    
          int tmp = 0;
          int flag = 0;
          for (int i = 0; i < sz - 1; ++i) {
    
    
              for (int j = 0; j < sz - i - 1; ++j) {
    
    
                  if (arr[j] > arr[j + 1]) {
    
    
                      tmp = arr[j];
                      arr[j] = arr[j + 1];
                      arr[j + 1] = tmp;
                      flag = 1;
                  }
              }
              if (!flag) {
    
    
                  return;
              }
          }
      }

50.寻找单身狗

void Find() {
    
    
          int arr[] = {
    
    1, 2, 3, 4, 5, 1, 2, 3, 4};
       int i = 0;
          int ret = 0;
          //总数组的长度除以第一个数组的长度
          int sz = sizeof(arr) / sizeof(arr[0]);//计算数组的元素个数
   
          for (i = 0; i < sz; i++) {
    
    
              ret = ret ^ arr[i];
          }
          printf("单身狗是:%d\n", ret);
      }

51.模拟字符串函数

1.strcpy()

#include <assert.h>
        char *my_strcpy1(void* dst, const void*src){
    
    
            assert(dst && src);
            char *ret = dst;
            while((*dst++ = *src++)){
    
    
                ;
            }
            return ret;
        }

2.strlen()

int my_strlen1(const char*str){
    
    
            //计数器方式
            int count = 0;
            while (*str){
    
    
                count++;
                str++;
            }
            return count;
        }

int my_strlen2(const char*str){
    
    
    //不能创建临时变量计数器--递归
    if (*str == '\0'){
    
    
        return 0;
    }else{
    
    
        return 1+my_strlen2(str+1);
    }
}

int my_strlen3(const char*str){
    
    
    char *p = str;
    //结束指针-初指针
    while (*p != '\0'){
    
    
        p++;
    }
    return p-str;
}

3.strcat()

#include <assert.h>
void *my_strcat1(char *dst, const char* src){
    
    
    assert(dst && src);
    char *ret = dst;
    while(*dst++){
    
    
        ;
    }
    while(*dst++==*src++){
    
    
        ;
    }
    return ret;
}

4.strcmp()

int my_strcmp(const char *dst, const char *src){
    
    
    assert(dst && src);
    int ret=0;
    // (!...) && *src中*src作用可以保证src指针不越界,当dst指针为\0之时,更具判断就可以知道dst不会继续后移【方法很巧妙】
    while (!(    ret=    *(unsigned char *)dst-*(unsigned char *)src    )     && *src){
    
    
        ++src,++dst;
    }
    if (ret < 0){
    
    
        ret = -1;
    }else if(ret > 0){
    
    
        ret = 1;
    }
    return ret;
}

5.strstr()

#include <assert.h>
const char *my_strstr1(const char *str, const char *sub_str){
    
    
    assert(str && sub_str);
    //确定起始位置
    while (*str){
    
    
        //局部比较
        const char*p = str;
        const char*q=sub_str;
        while (*p && *q && *p == *q){
    
    //此处一定可以保证sub_str不为0,p不越界
            p++;
            q++;
        }
        if (*q == '\0'){
    
    //当越界发生时,无论是sub_str还是p只需要判断sub_str为0,就代表检查完毕可以返回原始的str指针地址
            return str;
        }
        str++;//第一次判断什么都没有,重新确立起始位置
    }
    return NULL;
}

6.strtok()简介

/*
 * strtok
 * char *strtok(char *str, const char *sep);
 * sep参数是一个字符串,定义了用作分隔符的字符合集
 * 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记
 * strtok函数找到str中的下一个标记,并将其用\0结尾,返回一个指向这个标记的指针(注:strtok函数会改变被操作的字符串,所以使用strtok函数切分的字符串一般都是林hi拷贝的内容并且可修改)
 * strtok函数的第一个参数为NULL,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置
 * strtok函数第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记
 * 如果字符串中不存在更多的标记,则返回一个NULL指针
 */

7.memcpy()

#include <assert.h>

void *my_memcpy(void *dst, const void *src, int num) {
    
    
    assert(dst && src);
    char *_dst = (char *) dst;
    const char *_src = (const char *) src;

    while (num) {
    
    
        *_dst = *_src;
        _dst++, _src++, num--;
    }
    return dst;
}

8.memmove()

#include <assert.h>

void *my_memmove(void *dst, const void *src, int num) {
    
    
    assert(dst && src);
    char *_dst = (char *) dst;
    const char *_src = (const char *) src;

    if (_dst > _src && _dst < _src + num) {
    
    
        //right -> left:
        _dst = _dst + num - 1;
        _src = _src + num - 1;
        while (num) {
    
    
            *_dst++ = *_src++;
            num--;
        }
    } else {
    
    
        //left->right
        while (num) {
    
    
            *_dst++ = *_src++;
            num--;
        }
    }
    return dst;
}

9.qsort()模拟实现

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

void swap(char *src, char *dst, int sz) {
    
    
    char tmp;
    while (sz) {
    
    
        tmp = *src;
        *src = *dst;
        *dst = tmp;
        src++;
        dst++;
        sz--;
    }
}

void my_qsort(void *arr, int num, int sz, int (*comp)(const void *, const void *)) {
    
    
    assert(arr);
    assert(comp);
    char *e = (char *) arr;
    for (int i = 0; i < num - 1; ++i) {
    
    
        int flag = 0;
        for (int j = 0; j < num - i - 1; ++j) {
    
    
            if (comp(e + j * sz, e + (j + 1) * sz) > 0) {
    
    
                flag = 1;
                swap(e + j * sz, e + (j + 1) * sz, sz);
            }
        }
        if (flag == 0) {
    
    
            break;
        }
    }
}

int CompInt(const void *_xp, const void *_yp) {
    
    
    int *xp = (int *) _xp;
    int *yp = (int *) _yp;
    if (*xp > *yp) {
    
    
        return 1;
    } else if (*xp < *yp) {
    
    
        return -1;
    } else {
    
    
        return 0;
    }
}

void Print(void arr[], int sz) {
    
    
    for (int i = 0; i < sz; ++i) {
    
    
        printf("%d ", arr[i]);
    }
}

int main() {
    
    
    int arr1[] = {
    
    1, 3, 5, 7, 9, 2, 4, 6, 8, 10};
    int num1 = sizeof(arr1) / sizeof(arr1[0]);
    my_qsort(arr1, num1, sizeof(int), CompInt);
    Print(arr1, num1);

    int *arr2[] = {
    
    "b1234", "a1234", "4321", "abcdef"};
    int num2 = sizeof(arr2) / sizeof(arr2[0]);
    Print(arr2, num2);
    return 0;
}

52.喝汽水,1瓶汽水1元,2个空瓶可以换一瓶汽水,给20元,可以多少汽水

void soda(){
    
    
    int money = 0;
    int total = 0;
    int empty = 0;
    scanf("%d", &money);
    total = money;
    empty = money;
    while (empty > 1){
    
    
        total += empty/2;
        empty = empty/2 + empty%2;
    }
    printf("%d\n", total);
}

53.数组奇数放前边,偶数放后边

void swap_arr(int arr[], int sz) {
    
    
    int left = 0;
    int right = sz - 1;
    while (left < right) {
    
    
        while ((left < right) && arr[left] % 2 == 1) {
    
    
            ++left;
        }
        while ((left < right) && arr[right] % 2 == 0) {
    
    

            --right;
        }
        if (left < right) {
    
    
            int tmp;
            tmp = arr[left];
            arr[left] = arr[right];
            arr[right] = tmp;
        }
    }
}

54.日本某地发生了一件谋杀案,警察通过排查确定杀人凶手必为4个嫌疑犯的一个。

以下为4个嫌疑犯的供词:
A说:不是我。
B说:是C。
C说:是D。
D说:C在胡说

void killer() {
    
    
    int killer = 0;
    for (int killer = 'a'; killer < 'd'; ++killer) {
    
    
        if ((killer != 'a') + (killer == 'c') + (killer != 'd') == 3) {
    
    
            printf("%c\n", killer);
        }
    }
    return 0;
}

55.5位运动员参加了10米台跳水比赛,有人让他们预测比赛结果:

A选手说:B第二,我第三;
B选手说:我第二,E第四;
C选手说:我第一,D第二;
D选手说:C最后,我第三;
E选手说:我第四,A第一;
比赛结束后,每位选手都说对了一半,请编程确定比赛的名次。

void rank() {
    
    
    int a = 0, b = 0, c = 0, d = 0, e = 0;
    for (int a = 1; a <= 5; ++a) {
    
    
        for (int b = 1; b <= 5; ++b) {
    
    
            for (int c = 1; c <= 5; ++c) {
    
    
                for (int d = 1; d <= 5; ++d) {
    
    
                    for (int e = 1; e <= 5; ++e) {
    
    
                        if (((b == 2) + (a == 3) == 1) &&
                            ((b == 2) + (e == 4) == 1) &&
                            ((c == 1) + (d == 2) == 1) &&
                            ((c == 5) + (d == 3) == 1) &&
                            ((e == 4) + (a == 1) == 1)
                                ) {
    
    
                            if (a * b * c * d * e == 120) {
    
    
                                printf("a=%d,b=%d,c=%d,d=%d,d=%d\n", a, b, c, d, e);
                            }
                        }
                    }
                }
            }
        }
    }
}

56. 汉诺塔

//n:盘子个数    pos1:起始位置 pos2:中途位置 pos3:目的位置
void move(char pos1, char pos2){
    
    
    printf("%c->%c ", pos1, pos2);
}
void hanoi(int n, char pos1, char pos2, char pos3){
    
    
    if(n==1){
    
    
        move(pos1, pos3);
    }else{
    
    
        hanoi(n-1, pos1, pos3, pos2);
        move(pos1, pos3);
        hanoi(n-1, pos2, pos1, pos3);
    }
}

57.青蛙跳台阶:一个青蛙每次跳1阶或2阶,问有几跳法

int JumpFloor(int target){
    
    
    if(target == 1){
    
    
        return 1;
    }else if(target == 2){
    
    
        return 2;
    }else{
    
    
        return JumpFllor(n-1)+JumpFloor(n-2);
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_45364220/article/details/117019717