1
头图

前言

看到这篇博客的同学们,到今天为止,我们的c语言初级部分讲解就结束了(可能有的同学好奇我的标题不是写的15天么,这才七天,哈哈,因为我们接下来就要开始进入c++的世界了,算是c语言的进阶,我今天整理发布的曾经自学的笔记相对有些复杂,涉及指针高级运算,今天的内容不求掌握,只求简单理解就好,即使没懂,也没关系啦,楼主纯手动码字不易,还望珍惜。欢迎关注,多和我交流。

0. 复习

0.1 结构体

是一种复合数据类型,可以将多个不同类型得变量给捏在一起。一般用于代表某一个整体得信息。
比如:学生信息有学生姓名,年龄,学号.... 贪吃蛇的 速度 血量 长度.....
语法:

struct 类型名
{
  字段1类型   字段1名字; //字段也叫做成员
  字段2类型   字段2名字;
    .....;
};
struct  类型名  变量名 = {初始值};
C语言中定义结构体变量的时候,需要加上struct.c++不需要
C语言的程序员为了不写这个struct,有了一种类型定义的写法
typedef struct _类型名
{
  字段1类型   字段1名字; //字段也叫做成员
  字段2类型   字段2名字;
    .....;
}类型名,*P类型名;
typedef struct _STUDENT
{
  //
}STUDENT,*PSTUDENT;

使用结构体的时候,按照成员本身的类型去使用。

0.2 联合体

和结构体语法是类型的,区别在于联合的所有成员是共享内存的。
比较适合用在 成员互斥的情况下(一个有效,其他的就都是无效的)

0.3 类型定义

typedef int INT; //INT 就是int的别名

0.4 堆空间

申请:malloc
释放:free
设置内存中的值:memset
拷贝内存:memcpy
一些概念:
悬空指针:释放之后,没有被置为nullptr的指针
野指针:没有初始化的指针
悬空指针和野指针都指向无效区域。
正在运行的程序,有5个内存区域:
静态数据区:全局变量,static局部变量所在的区域
常量区:字符串常量所在的区域
代码区:代码所在的区域
栈区:局部变量和函数的参数都在栈区
堆区:malloc 申请的空间,是堆区的。
图片.png
作用域:变量起作用的一个范围
生存期:变量存在的一个时期
局部变量,参数:进入函数,有效,此时在栈区创建出来。离开函数,失效,此时自动销毁。
静态局部变量:进入函数之前就被创建。但是在函数外面是不能使用的。离开函数,也不会被销毁。
堆区:申请就存在,释放就销毁

1. 指针进阶

1.1 指针的算术运算

1.1.1 指针+(-) 整数

#include <stdio.h>
int main()
{
    int a = 100;
    int* p = NULL;
    p = (int*)100;
    //1. 一个地址本质来说也是一个数字
    //但是地址一般都是由&得到的,或者malloc函数返回的
    //如果我们随便写个地址,这个地址通常都是不能访问
    //2. 做加法运算
    //a+n 一个整型做加法运算,为了得到一个算术结果
    //p+n 指针做加法运算,是为了得到偏移为n的元素的位置。
    //有了这个位置,就可以通过*得到偏移为n的位置的数据
    printf("%d\n", a );
    printf("%d\n", a+2);
    printf("%d\n", p);
    printf("%d\n", p + 2);
    //3. 这个特性有什么用呢???
    int arr[10] = { 4,5,20,40,10,6,7,8,9,10 };
    p = arr;//能赋值,说明类型相似
    for (int i = 0; i < 10; i++)
    {
        printf("%x ", p + i);
        printf("%d \n", *(p + i));
    }
    //4. double* char*  short*  结构体*
    typedef struct _TEST
    {
        int a;
        double b;
        int c;
    }TEST,*PTEST;
    double* p2 = (double*)100;
    char* p3 = (char*)100;
    short* p4 = (short*)100;
    //下面两种写法等价的
    PTEST p5 = (PTEST)100;
    TEST* p6 = (TEST * )100;
    printf("%d\n", p2);
    printf("%d\n", p3);
    printf("%d\n", p4);
    printf("%d\n", p5);
    printf("%d\n", p6);

    printf("%d\n", p2+1);//108
    printf("%d\n", p3+1);//101
    printf("%d\n", p4+1);//102
    printf("%d\n", p5+1);//124
    printf("%d\n", p6+1);//124



    return 0;
}

1.1.2 指针- 指针(大概了解即可)

要求:两个指针的类型必须是一致的。得到的结果是两个地址之间能够存储下多少个此类型
图片.png

1.2 指针和一维数组

指针和一维数组有非常多的相同点:

#include <stdio.h>
int main()
{
    //1. 对于不相似的类型,C语言是不让直接赋值的
    int nNum1 = 100;
    short sNum1 = 0;
    sNum1 = nNum1;
    int* p1 = NULL;
    short* p2 = NULL;
    p1 = &nNum1;
    p2 = &sNum1;
    //类型非常不同,不能直接赋值的
    //p = nNum1;不行
    //short*和int*也是不能直接赋值的
    //运算规则不同
    //p1 = p2;
    //指针和数组可以直接赋值
    int arr[10] = { 4,5,20,40,10,6,7,8,9,10 };
    int* p = arr;//能赋值,说明类型相似
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", *(p + i));
        printf("%d ", *(arr + i));
        printf("%d ", arr[i]);
        printf("%d \n", p[i]);
    }

    return 0;
}

图片.png
是否就可以说 指针就是数组呢???
1.指针是一个变量,可以指向其他位置
2.数组名是数组的起始地址,是一个常量,不能改变的
从sizeof的角度说,他俩也不同。
在32位,任何一个指针,都是四个字节的变量。

1.3 一级指针的使用场景

1.3.1 通过函数去修改外部的变量

#include <stdio.h>
void Test(int* a)
{
    *a = 500;
}

int main()
{
    int nNum = 100;
    Test(&nNum);
    printf("%d", nNum);
    return 0;
}

1.3.2 将数组作为一个参数进行传递的时候

#include <stdio.h>

//         int* Test
//         int Test[]
//         int Test[100]
int GetAdd(int Test[10])
{
    int s = 0;
    printf("%d\n",sizeof(Test));
    for (int i = 0; i < 10; i++)
    {
        s += Test[i];
    }
    return s;
}
int main()
{
    //假如有一个数组
    int arrTest[10] = { 4,3,5,7,8,1,2,9,0,10 };
    //通过函数求所有元素的和
    //数组名是一个地址,接收地址的只能是指针
    printf("%d\n", sizeof(arrTest));
    int n = GetAdd(arrTest);
    printf("%d", n);
    return 0;
}

1.3.3 使用堆空间的时候

#include <stdio.h>
#include <stdlib.h>
//         int* Test
//         int Test[]
//         int Test[100]
int GetAdd(int Test[10], int nCount)
{
    int s = 0;
    printf("%d\n", sizeof(Test));
    for (int i = 0; i < nCount; i++)
    {
        s += Test[i];
    }
    return s;
}
int main()
{
    int* p = (int*)malloc(5 * sizeof(int));
    for (int i = 0; i < 5; i++)
    {
        printf("请输入一个数据:");
        scanf_s("%d", &p[i]);
        //scanf_s("%d", p+i);
    }
    int n = GetAdd(p, 5);
    printf("和为%d", n);

    return 0;
}

1.4 指针和二维数组(对初学者来说难度很大,尽量理解就好,没有理解也没有关系,不必气馁)

#include <stdio.h>

int main()
{
    int arrTest1[3][4] =
    {   1,2,3,4,
        5,6,7,8,
        9,10,11,12 };
    int arrTest2[10][4] =
    { 1,2,3,4,5,6,7,8,9,10,11,12 };
    int arrTest3[3][5] =
    { 1,2,3,4,5,6,7,8,9,10,11,12 };
    //printf("%p\n", arrTest1);
    //二维数组名也是地址
    //但是类型并非是普通的指针
    //int* p1 = arrTest1;
    //类型和  【数组指针】  相似
    int(*p1)[4] = NULL;
    p1 = arrTest1;//赋值成功,类型确实相似
    p1 = arrTest2;//这个也能成功,就是列数对上了就行
    //p = arrTest3; 不能成功
    int(*p2)[5] = arrTest3;
    //数组指针有什么特点:
    //数组指针+1是加了一排
    int(*p3)[4] = ( int(*)[4] )100;
    //printf("%d ", p3);
    //printf("%d", p3 + 1);
    //数组指针和二维数组加法规则一致
    p1 = arrTest1;
    printf("%p\n", p1);
    printf("%p\n", p1+1);
    printf("%p\n", p1 + 2);
    printf("%p\n", arrTest1);
    printf("%p\n", arrTest1 + 1);
    printf("%p\n", arrTest1 + 2);
    //解一次引用
    //对于数组指针和二维数组而言
    //解一次引用,还是那个地址值不变
    //但是 类型发生了变化
    printf("------------------\n");
    printf("%p\n", p1);      //数组指针类型
    printf("%p\n", *p1);     //一级指针类型,指向的是下标为0的那一排
    printf("%p\n", p1 + 1);  //数组指针类型
    printf("%p\n", *(p1 + 1)); //一级指针类型,指向的是下标为1的那一排
    printf("------------------\n");
    printf("%p\n", arrTest1);
    printf("%p\n", *arrTest1);
    printf("%p\n", arrTest1 + 1);
    printf("%p\n", *(arrTest1 + 1));
    //区别
    printf("------------------\n");
    printf("%p\n", p1+1);
    printf("%p\n", p1 + 1+1);//再往下找一排+16
    printf("%p\n", *(p1 + 1) + 1);//在本排中找下一个元素+4

    printf("%p\n", *(*(p1 + 1) + 1));
    return 0;
}

代码有点冗长和绕,一张图理解总结下:
图片.png

    //遍历二维数组
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            printf("%d", p1[i][j]);
            printf("%d", *(p1[i]+j));
            printf("%d", *(*(p1+i)+j));
            printf("%d", (*(p1 + i))[j]);

            printf("%d", arrTest1[i][j]);
            printf("%d", *(arrTest1[i] + j));
            printf("%d", *(*(arrTest1 + i) + j));
            printf("%d", (*(arrTest1 + i))[j]);
        }
    }

1.5 使用场景

1.5.1 传参

1.5.2 使用堆空间

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
//         int (*test)[4]
//         int test[][4]
//         int test[8][4]
int GetAdd(int(*test)[4],int nRowCount)
{
    printf("%d", sizeof(test));
    int s = 0;
    for (int i = 0; i < nRowCount; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            s += test[i][j];
        }
    }
    return s;
}
int main()
{
    //1. 数组传参
    int arr[3][4] = { 1,2,3,4,
        5,6,7,8,
        9,10,11,12 };
    printf("%d", sizeof(arr));
    GetAdd(arr,3);
    //2. 使用堆空间,可以把堆空间当数组来用
    int(*p)[4] = (int(*)[4])malloc(5 * 4 * sizeof(int));
    memset(p, 0, 5 * 4 * sizeof(int));
    p[1][1] = 10;
    GetAdd(p, 5);
    return 0;
}

1.6 数组指针和指针数组

// 数组指针 是指针
// 指针数组 是数组

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

int main()
{
    //数组指针是一个指针
    //可以把一块内存区域,按照二维数组的访问去使用
    int(*p1)[4] = nullptr;
    int arrTest[5][4] = { 1,2,3 };
    p1 = arrTest;
    //指针数组
    int nNum = 0;
    int arr[3] = { 1,2,3 };
    int* p2[4] = { NULL,NULL,NULL,NULL };
    p2[0] = (int*)malloc(5 * sizeof(int));
    p2[1] = &nNum;
    p2[2] = arr;
    p2[3] = (int*)malloc(6 * sizeof(int));
    //虽然两个语法是不同的概念
    //但是在使用的时候有相似性
    arrTest[1][1] = 100;
    //这个代表 2号指针 指向的位置 里面下标为1的地方
    p2[2][1] = 20;



    return 0;
}

字符类型

//字符串
    char arr1[3][20] = { "xiaoming","xiaobai","xiahui" };
    printf("%s", arr1[0]);
    printf("%s", arr1[1]);
    arr1[0][1] = 'a';
    const char* arr2[3] = { "xiaoming","xiaobai","xiahui" };
    printf("%s", arr2[0]);
    printf("%s", arr2[1]);
    //arr2[0][1] = 'a';

1.7 结构体指针

int main()
{
    TEST stc = {10,2.5,20};
    PTEST pstc = NULL;
    pstc = &stc;
    printf("%d %d\n", pstc->a, stc.a);
    printf("%lf %lf\n", pstc->b, stc.b);
    printf("%d %d\n", pstc->c, stc.c);

    pstc = (PTEST)malloc(sizeof(TEST) * 3);
    memset(pstc, 0, sizeof(TEST) * 3);
    for (int i = 0; i < 3; i++)
    {
        (pstc + i)->a = i * 10+1;
        pstc[i].b = 3.3;
    }
    return 0;
}

1.8 二级指针

图片.png

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
void Fun(int** p, int n)
{
    *p = (int*)malloc(n * sizeof(int));
    memset(*p, 0, sizeof(int) * n);
}

int main()
{
    int a = 100;
    int* p1 = &a;
    //二级指针
    int** p2 = &p1;

    //应用,假如说我希望在一个函数中申请堆空间,出了函数
    //这个堆空间还能使用
    int* p3 = NULL;
    Fun(&p3, 5);


}

2. 文件操作

2.1 文件的简单分类

1.文本文件(使用记事本能够直接打开并查看显示的问题),本质上存储的是文本的编码
2.二进制文件(电影,音乐,图片,通常是给特定的软件去使用的)

2.2 关于路径的问题

我们在windows中,去定位一个文件,通常都是使用路径的,去找到某一个文件
路径有两种形式:
1.绝对路径
D:\Test\abc.txt
2.相对路径
是从当前目录出发,去寻找某一个文件
. 代表当前目录
.. 代表上一级目录

例子:
.\abc.txt
..\test\abc.txt
什么是当前目录:
1.当我们F5 调试运行的时候,代码所在的目录就是当前目录
2.当我们直接运行程序的时候,exe文件所在的目录就是当前目录

2.3 文件操作的基本步骤

1.打开文件 fopen_s
图片.png

2.读写文件

  从文件中读取数据              往文件中写入数据
       fgetc                                  fputc
       fgets                                  fputs
       fscanf_s                             fprintf
       fread                                  fwrite

文件读取写入函数-都是字符

#include <stdio.h>
int main()
{
    FILE* pFile = nullptr;
    ////1. 打开一个文件,以w的方式,文件不存在就会创建一个
    ////这里的文件指针,就是代表一个打开的文件
    //pFile = nullptr;
    //fopen_s(&pFile, "..\\debug\\abc.txt", "w");

    ////2. 写入内容
    //// 2.1 fputc
    ////fputc('A', pFile);
    ////fputc('B', pFile);
    ////fputc('C', pFile);
    //// 2.2 fputs
    //// fputs("hello world", pFile);
    //// 2.3 fprintf
    //fprintf(pFile, "%d %d %lf", 10, 20, 8.55);
    ////3. 关闭文件
    //fclose(pFile);

    //读取
    //1. 打开文件
    pFile = nullptr;
    fopen_s(&pFile, "..\\debug\\abc.txt", "r");
    //2. 读取文件
    //2.1 fgetc
    //char cCh = 0;
    //while (cCh!=EOF)
    //{
    //    cCh = fgetc(pFile);
    //    printf("%c", cCh);
    //}
    //2.2 fgets
    //char buf[20] = {};
    //fgets(buf,20,pFile);
    //2.3 fscanf_s
    int a = 0;
    int b = 0;
    double c = 0;
    fscanf_s(pFile, "%d %d %lf", &a, &b, &c);
    //3. 关闭文件
    fclose(pFile);
}

文件读取写入函数- 内存读写

#include <stdio.h>
int main()
{
    FILE* pFile = nullptr;

    ////1. 打开一个文件,以w的方式,文件不存在就会创建一个
    ////这里的文件指针,就是代表一个打开的文件
    //pFile = nullptr;
    //fopen_s(&pFile, "..\\debug\\abc.txt", "w");

    ////2. 写入内容
    //int arr[7] = { 10,20,30,40,50,60,70 };
    ////这个函数,是将内存中数据写入到文件中
    ////写入了4*7  也就是28个字节
    //fwrite(arr, 4, 7, pFile);
    ////3. 关闭文件
    //fclose(pFile);

    ////读取
    ////1. 打开文件
    pFile = nullptr;
    fopen_s(&pFile, "..\\debug\\abc.txt", "r");
    ////2. 读取文件
    int arr[7] = { };
    fread(arr, 4, 7, pFile);
    ////3. 关闭文件
    //fclose(pFile);
}

3.关闭文件-fclose

2.4 打开文件的模式 加不加 b的问题

图片.png
所有的打开的模式,都可以添加一个b,比如:wb rb ab w+b r+b a+b。
有了这个b的模式,叫做二进制模式,没有b的就是文本模式。
文本模式,写入的时候,遇到了0A 就会转为0D 0A 。读取的时候遇到了 0D 0A 就会转换为0A读取进来。
文本模式,遇到了0A转换为0D 0A 如下:
图片.png
因为在windows平台,\n的asc码 是 0A ,但是只有\n不能换行,要显示换行 需要是 \r \n 就是0D 0A。
如果是二进制模式,那么就不会转换
图片.png
唯一的区别就在于会进行微小的转换。
一定要成对使用,写入的时候 加了b ,读取的时候也应该加

4.补充函数:

                  fseek
                  ftell

有一个文件读写位置。在读取或者写入时会自动设置这个位置。
比如一个文件有100个字节。刚刚打开的时候,位置是在0这个地方,读取了3个字节,位置就会往后调整3个字节。
fseek 的作用:设置读写位置

#include <stdio.h>
int main()
{
    FILE* pFile = nullptr;
    ////1. 打开一个文件,以w的方式,文件不存在就会创建一个
    ////这里的文件指针,就是代表一个打开的文件
    //pFile = nullptr;
    //fopen_s(&pFile, "..\\debug\\abc.txt", "wb");

    ////2. 写入内容
    //fputs("Hello world", pFile);
    ////3. 关闭文件
    //fclose(pFile);
    fopen_s(&pFile, "..\\debug\\abc.txt", "rb");
    char cCh = fgetc(pFile);
    cCh = fgetc(pFile);
    cCh = fgetc(pFile);
    cCh = fgetc(pFile);
    //下面这个函数,能够调整读写的位置
    //SEEK_END 以结尾为一个锚点
    //SEEK_SET 以开始为锚点
    //SEEK_CUR 以现在的位置为锚点
    fseek(pFile, 0, SEEK_SET);
    cCh = fgetc(pFile);
    cCh = fgetc(pFile);
}

ftell的作用:是查看当前的读写的位置在哪??

#include <stdio.h>
int main()
{
    FILE* pFile = nullptr;
    ////1. 打开一个文件,以w的方式,文件不存在就会创建一个
    ////这里的文件指针,就是代表一个打开的文件
    //pFile = nullptr;
    //fopen_s(&pFile, "..\\debug\\abc.txt", "wb");

    ////2. 写入内容
    //fputs("Hello world", pFile);
    ////3. 关闭文件
    //fclose(pFile);
    fopen_s(&pFile, "..\\debug\\abc.txt", "rb");
    char cCh = fgetc(pFile);
    cCh = fgetc(pFile);
    cCh = fgetc(pFile);
    cCh = fgetc(pFile);
    //fseek这个函数,能够调整读写的位置
    //SEEK_END 以结尾为一个锚点
    //SEEK_SET 以开始为锚点
    //SEEK_CUR 以现在的位置为锚点
    fseek(pFile, 0, SEEK_SET);
    cCh = fgetc(pFile);
    cCh = fgetc(pFile);
    //ftell 告诉当前的文件读写位置在哪
    int nSize = ftell(pFile);
    cCh = fgetc(pFile);
    nSize = ftell(pFile);
    //fseek和ftell配合可以获取文件大小
    fseek(pFile, 0, SEEK_END);
    nSize = ftell(pFile);
    printf("当前文件大小就是%d字节", nSize);


}

注意:
使用这些函数的时候,最好成对使用。
写入的时候如果加了 b,读取的时候也一定要加上。
在一次的打开和关闭之间,只进行一次读写操作

作为基础部分的结束,奉上我自己曾经总结的c语言经典题供进一步强化

(持续更新,已更新至8月25日)C语言经典题集合


瞿小凯
1.3k 声望593 粉丝