• [Re] 指针&结构体&文件


    指针

    地址与指针变量

    内存地址

    • 将内存抽象成一个很大的一维字符数组。
    • 编码就是对内存的每一个字节分配一个 32 位或 64 位的编号(与 32 位或者 64 位处理器相关)。
    • 这个内存编号我们称之为内存地址。
    • 内存中的每一个数据都会分配相应的地址,使用 sizeof(变量名|数据类型) 可得出数据所占地址单元的个数。
      # include <stdio.h>
      // 访问变量的两种方式:1. 变量名访问 2. 内存地址访问
      int main() {
          int a = 10;
          float b = 20;
          char c = 'a';
          printf("int 类型所占字节数:%d
      ", sizeof(a)); // 4
          // 占位符 %p 打印数据的内存地址,unsigned int 十六进制表示
          printf("int a 的内存地址:%p
      ", &a); // 000000000062FE1C
          printf("float b 的内存地址:%p
      ", &b); // 000000000062FE18
          printf("char c 的内存地址:%p
      ", &c); // 000000000062FE17
          printf("sizeof(p): %d", sizeof(p)); // 8
          return 0;
      }
      

    指针和指针变量

    • 内存区的每一个字节都有一个编号,这就是"地址"。
    • 如果在程序中定义了一个变量,在对程序进行编译或运行时,系统就会给这个变量分配内存单元,并确定它的内存地址(编号)
    • 指针和地址概念不同,指针是一种地址变量,通常也叫指针变量;而地址则是地址变量的值
    • 指针不是类型!真正的类型是地址,指针变量只是存储地址这种数据类型的变量
    • 地址是内存单元的编号,指针变量是存放地址的变量。
    • 通常我们叙述时会把指针变量简称为"指针",实际他们含义并不一样。
    # include <stdio.h>
    
    // 访问变量的两种方式:1. 变量名访问 2. 内存地址访问
    int main() {
        int a = 1101;
        int* p;
        // &:取地址运算符
        p = &a;
        printf("&a: %p
    ", &a); // 000000000062FE14
        printf(" p: %p
    ", p); // 000000000062FE14
        // *:取值运算符
        printf("*p: %d
    ", *p); // 1101
        *p = 13;
        printf(" a: %d
    ", a); // 13
        printf("*p: %d
    ", *p); // 13
        printf("sizeof(p): %d", sizeof(p)); // 8
        return 0;
    }
    

    注意:& 可以取得一个变量在内存中的地址。但是,不能取寄存器变量,因为寄存器变量不在内存里,而在 CPU 里面,所以是没有地址的。

    通过指针间接修改主函数中变量的值:

    函数形参是指针

    首先,C语言之所以把作为形参的数组看作指针,并非因为数组名可以转换为指针,而是因为当初ANSI委员会制定标准的时候,从C程序的执行效率出发,不主张参数传递时复制整个数组,而是传递数组的首地址,由被调函数根据这个首地址处理数组中的内容。那么谁能承担这种“转换”呢?这个主体必须具有地址数据类型,同时应该是一个变量,满足这两个条件的,非指针莫属了。

    要注意的是,这种“转换”只是一种逻辑看法上的转换,实际当中并没有发生这个过程,没有任何数组实体被转换为指针实体。另一方面,大家不要被“转换”这个字眼给蒙蔽了,转换并不意味着相同,实际上,正是因为不相同才会有转换,相同的话还转来干吗?这好比现在社会上有不少人“变性”,一个男人可以“转换”为一个女人,那是不是应该认为男人跟女人是相同的?这不是笑话么。

    第二,函数参数传递的过程,本质上是一种赋值过程。C89对函数调用是这样规定的:函数调用由一个后缀表达式(称为函数标志符,function designator)后跟由圆括号括起来的赋值表达式列表组成,在调用函数之前,函数的每个实际参数将被复制,所有的实际参数严格地按值传递。因此,形参实际上所期望得到的东西,并不是实参本身,而是实参的值或者实参所代表的值!

    举个例来说,对于一个函数声明:void fun(int i); 我们可以用一个整数变量int n作实参来调用fun,就是fun(n);当然,也正如大家所熟悉的那样,可以用一个整数常量例如10来做实参,就是fun(10);那么,按照第二个疑问的看法,由于形参是一个整数变量,而10可以作为实参传递给i,岂不就说明10是一个整数变量吗?这显然是谬误。

    实际上,对于形参i来说,用来声明i的类型说明符int,所起的作用是用来说明需要传递给i一个整数,并非要求实参也是一个整数变量,i真正所期望的,只是一个整数,仅此而已,至于实参是什么,跟i没有任何关系,它才不管呢,只要能正确给i传递一个整数就OK了。当形参是指针的时候,所发生的事情跟这个是相同的。

    指针形参并没有要求实参也是一个指针,它需要的是一个地址,谁能给予它一个地址?显然指针、地址常量和符号地址常量都能满足这个要求,而数组名作为符号地址常量正是指针形参所需要的地址,这个过程就跟把一个整数赋值给一个整数变量一样简单!

    当数组名作为函数参数时,函数的形参会退化为指针。

    数组

    指针加法:

    #include <stdio.h>
    
    int main(void) {
        int arr[] = {1,2,3,4,5};
        int* pArr = arr;
        printf("arr: %d
    ", sizeof(arr)); // 20
        printf("pArr: %d
    ", sizeof(pArr)); // 8
        printf("&arr: %p
    ", &arr); // &arr: 000000000062FE00
        // 首元素地址,同时与整个数组地址重合
        printf("arr: %p
    ", arr); // arr: 000000000062FE00
        printf("arr[0]: %p
    ", arr+0); // 000000000062FE00
        printf("arr[1]: %p
    ", arr+1); // 000000000062FE04
        printf("arr[2]: %p
    ", arr+2); // 000000000062FE08
        printf("arr[3]: %p
    ", arr+3); // 000000000062FE0C
        printf("arr[4]: %p
    ", arr+4); // 000000000062FE10
        // 区分 &arr+1 和 arr+1
        printf("&arr+1: %p
    ", &arr + 1); // 000000000062FE14
        return 0;
    }
    

    指针减法:

    #include <stdio.h>
    
    int main(void) {
        int step;
        char arr[] = "HelloWorld!";
        char* p = &arr[4];
        step = p - arr;
        printf("%d
    ", step); // 4
        return 0;
    }
    

    指针数组

    int main(void) {
        int a = 1;
        int b = 2;
        int c = 3;
        // 指针类型的数组
        int* arr[] = {&a, &b, &c};
        printf("a = %d
    ", *arr[0]);
        printf("b = %d
    ", **(arr+1));
        return 0;
    }
    

    多级指针

    #include <stdio.h>
    
    int main(void) {
        int a = 10;
        int b = 20;
        // 使用一级指针接收变量地址
        int* p = &a;
        int* q = &b;
        // *p=123; // 间接改变变量的值
        // 使用二级指着接收一级指针的地址
        int** p1 = &p;
    
        *p1 = q; // 间接改变一级指针的值
        **p1 = 123; // 二级指针间接改变变量的值
    
        // 三级指针
        // int*** p2 = &p1;
    
        // 四级指针
        // int**** p3 = &p2;
        return 0;
    }
    

    结构体

    typedef

    typedef 为 C 语言的关键字,作用是为一种数据类型(基本类型或自定义数据类型) 定义一个新名字,不能创建新类型

    #define 不同,typedef 仅限于数据类型,而不是能是表达式或具体的值;#define 发生在预处理,typedef 发生在编译阶段。

    #include <stdio.h>
    
    typedef unsigned int ui;
    typedef unsigned long ul;
    
    int main(void)
    {
        ui num = 1101;
        ul id = 32042759;
        printf("%d
    ", num);
        printf("%ld
    ", id);
        return 0;
    }
    

    struct

    概述

    数组:描述一组具有相同类型数据的有序集合,用于处理大量相同类型的数据运算。

    有时我们需要将不同类型的数据组合成一个有机的整体,如:一个学生有学号/姓名/性别/年龄/地址等属性。显然单独定义以上变量比较繁琐,数据不便于管理。

    C 语言中给出了另一种构造数据类型——结构体。

    定义

    • 定义结构体变量的方式
      • 先声明结构体类型再定义变量名
      • 在声明类型的同时定义变量
      • 直接定义结构体类型变量(无类型名)
    • 结构体类型和结构体变量关系
      • 结构体类型:指定了一个结构体类型,它相当于一个模型,但其中并无具体数据,系统对之也不分配实际内存单元。
      • 结构体变量:系统根据结构体类型(内部成员状况)为之分配空间。

    简单使用:

    # include <stdio.h>
    # include <string.h>
    
    struct student {
        int id;
        char name[21]; // 一个中文是俩字符
        char sex;
        int age;
        char address[51];
    };
    
    int main() {
        // 定义结构体变量(数据类型: struct student)
        struct student stu1 = {1101, "刘佳琦", 'f', 22, "江苏省徐州市鼓楼区"};
    
        /*
        打印结构体变量信息
            如果是普通变量,通过点运算符操作结构体成员:stu.age
            如果是指针变量,通过->操作结构体成员:pStu->age
         */
        printf("id: %d
    ", stu1.id);
        printf("sname: %s
    ", stu1.name);
        printf("sex: %s
    ", stu1.sex == 'f' ? "女" : "男");
        printf("age: %d
    ", stu1.age);
        printf("address: %s
    ", stu1.address);
    
        // 修改结构体成员信息
        stu1.id = 13;
        // name 是数组类型,是个常量,不能修改
        // stu1.name = "刘源"; <<<<<< ERROR
        // 字符串拷贝(目标地址, 要拷贝的字符串)
        strcpy(stu1.name, "刘源");
        printf("sid: %d
    ", stu1.id);
        printf("sname: %s
    ", stu1.name);
        return 0;
    }
    

    结构体数组

    # include <stdio.h>
    
    /*
     为结构体其别名1
    typedef struct student STU;
    
    struct student {
        int id;
        char name[21];
        char sex;
        int age;
        char address[51];
    };
     */
    
    // 为结构体其别名2
    typedef struct student {
    	int id;
    	char name[21];
    	char sex;
    	int age;
    	char address[51];
    } STU;
    
    void bubbleSort(STU* ss, int len) {
        int i, j;
        STU temp;
        for(i = 0; i < len-1; i++)
            for(j = 0; j < len-i-1; j++)
                if(ss[j].age > ss[j+1].age) {
                    // 相同类型的两个结构体变量,可以相互赋值
                    // 把 ss[j] 成员变量的值拷贝到 temp 成员变量的内存中
                    // ss[j] 和 temp 只是成员变量的值一样而已,它们还是没有关系的两个变量
                    temp = ss[j];
                    ss[j] = ss[j+1];
                    ss[j+1] = temp;
                }
    }
    
    int main() {
        int i;
    
        struct student stu1 = {1, "abc1", 'm', 22, "address1"};
        STU stu2 = {2, "abc2", 'm', 14, "address2"};
        STU stu3 = {3, "abc3", 'f', 16, "address3"};
        STU stu4 = {4, "abc4", 'f', 18, "address4"};
        STU ss[4] = {stu1, stu2, stu3, stu4};
    
        bubbleSort(ss, 4);
    
        for(i=0; i<4; i++) {
            printf("编号:%d	",ss[i].id);
            printf("姓名:%s	",ss[i].name);
            printf("性别:%s	",ss[i].sex=='M'?"男":"女");
            printf("年龄:%d	",ss[i].age);
            printf("地址:%s
    ",ss[i].address);
        }
        return 0;
    }
    

    结构体嵌套结构体

    # include <stdio.h>
    # include <string.h>
    
    typedef struct {
        char schoolName[51];
        int level;
    } School;
    
    typedef struct student {
        int id;
        char name[21];
        char sex;
        int age;
        char address[51];
        School school;
    } STU;
    
    int main() {
        int i;
        School school = {"NUIST", 1};
        struct student stu1 = {1, "abc1", 'm', 22, "address1", school};
        STU stu2 = {2, "abc2", 'm', 14, "address2", school};
        STU stu3 = {3, "abc3", 'f', 16, "address3", school};
        STU stu4 = {4, "abc4", 'f', 18, "address4", school};
        STU ss[4] = {stu1, stu2, stu3, stu4};
    
        strcpy(ss[1].school.schoolName, "JMI");
        printf("%s
    ", ss[1].school.schoolName);
        return 0;
    }
    

    共用体(联合体)

    • 联合 union 是一个能在同一个存储空间存储不同类型数据的类型;
    • 联合体所占的内存长度等于其最长成员的长度倍数,也有叫做共用体;
    • 同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用;
    • 共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖;
    • 共用体变量的地址和它的各成员的地址都是同一地址。
    # include <stdio.h>
    
    union test1 {
        short s;
        int i;
        float f;
        double d; // 8
        char c;
    };
    
    union test2 {
        short s; // 2
        char c;
    };
    
    int main(void) {
        union test1 var1;
        union test2 var2;
        printf("%d
    ", sizeof(var1)); // 8
        printf("%d
    ", sizeof(var2)); // 2
    
        var1.i = 1101;
        printf("var1.int: %d
    ", var1.i); // 1101
        var1.c = 'a';
        printf("var1.int: %d
    ", var1.i); // 反正不是1101
        printf("var1.char: %c
    ", var1.c); // a
        return 0;
    }
    

    结构体构建链表

    没写完 ...

    # include<stdio.h>
    # include<malloc.h>
    # include<stdlib.h>
    
    typedef struct Node{
    	int data;
    	struct Node* pNext;
    }* PNode;
    
    PNode initList();
    void freeList(PNode);
    void addLast(PNode, int);
    void printList(PNode);
    int searchNode(PNode, int); // 第n个结点的数据
    void insertNode(PNode, int, int); // 在第n个结点后插入结点
    // 单链表反转
    // 链表中环的检测
    // 两个有序链表合并
    // 删除链表倒数第n个结点
    // 求链表中间结点
    
    int main(void) {
        int i;
        // Test-初始化链表
        PNode head = initList();
        // Test-链表尾部添加结点
        for(i = 1; i <= 5; i++) addLast(head, i);
        printList(head);
        // Test-查找第n个结点的data
        for(i = 1; i <= 5; i++) printf("%2d ", searchNode(head, i));
        // Test-在第n个结点后插入结点
        insertNode(head, 10, 1101);
        printList(head);
        // Test-释放链表存储空间
        free(head);
        return 0;
    }
    
    int searchNode(PNode head, int number) {
        PNode cur = head->pNext;
        int n = 1;
        if(number < 1) return -1;
        while(n != number && cur != NULL) {
            cur = cur->pNext; // 2 3
            n++; // 2 3
        }
        if(cur == NULL) return -1;
        else return cur->data;
    }
    
    void insertNode(PNode head, int afterN, int data) {
        PNode cur = head;
        PNode newNode;
        int n = 0;
        if(afterN < 0) return;
        while(n != afterN && cur != NULL) {
            cur = cur->pNext;
            n++;
        }
        if(cur == NULL) return;
        newNode = (PNode) (malloc(sizeof(struct Node)));
        newNode->data = data;
        newNode->pNext = cur->pNext;
        cur->pNext = newNode;
    }
    
    void printList(PNode head) {
        printf("
    ");
        PNode cur = head->pNext;
        while(cur != NULL) {
            printf("%2d ", cur->data);
            cur = cur->pNext;
        }
        printf("
    ");
    }
    
    void addLast(PNode head, int data) {
        PNode cur = head;
        PNode p = (PNode) (malloc(sizeof(struct Node)));
        p->data = data;
        p->pNext = NULL;
        while(cur->pNext != NULL) cur = cur->pNext;
        cur->pNext = p;
    }
    
    void freeList(PNode head) {
        PNode cur = head;
        PNode temp;
        while(cur != NULL) {
            temp = cur;
            cur = cur->pNext;
            free(temp);
        }
    }
    
    PNode initList() {
        PNode head = (PNode) (malloc(sizeof(struct Node))); // head 指向 [哨兵结点]
        head->pNext = NULL;
        return head;
    }
    

    文件操作

    文件类型指针

    在 C 语言中用一个指针变量指向一个文件,这个指针称为文件指针。

    typedef struct {
        short           level;	//缓冲区"满"或者"空"的程度
        unsigned        flags;	//文件状态标志
        char            fd;		//文件描述符
        unsigned char   hold;	//如无缓冲区不读取字符
        short           bsize;	//缓冲区的大小
        unsigned char   *buffer;//数据缓冲区的位置
        unsigned        ar;	    //指针,当前的指向
        unsigned        istemp;	//临时文件,指示器
        short           token;	//用于有效性的检查
    } FILE;
    
    

    FILE 是系统使用 typedef 定义出来的有关文件信息的一种结构体类型,结构中含有文件名、文件状态和文件当前位置等信息。

    声明 FILE 结构体类型的信息包含在头文件 "stdio.h" 中,一般设置一个指向 FILE 类型变量的指针变量,然后通过它来引用这些 FILE 类型变量。通过文件指针就可对它所指的文件进行各种操作。

    C 语言中有 3 个特殊的文件指针由系统默认打开,用户无需定义即可直接使用

    • stdin:标准输入,默认为当前终端(键盘),我们使用的 scanf、getchar 函数默认从此终端获得数据。
    • stdout:标准输出,默认为当前终端(屏幕),我们使用的 printf、puts 函数默认输出信息到此终端。
    • stderr:标准出错,默认为当前终端(屏幕),我们使用的 perror 函数默认输出信息到此终端。

    文件的打开与关闭

    文件的打开

    任何文件使用之前必须打开:

    第 1 个参数的几种形式:

    FILE *fp_passwd = NULL;
    
    // ------------ 相对路径 ------------
    // 打开当前目录 passdwd.txt 文件:源文件(源程序)所在目录
    FILE *fp_passwd = fopen("passwd.txt", "r");
    
    // 打开当前目录(test)下 passwd.txt 文件
    fp_passwd = fopen(". / test / passwd.txt", "r");
    
    // 打开当前目录上一级目录(相对当前目录) passwd.txt 文件
    fp_passwd = fopen(".. / passwd.txt", "r");
    
    // ------------ 绝对路径 ------------
    // 打开 C 盘 test 目录下一个叫 passwd.txt 文件
    fp_passwd = fopen("c:/test/passwd.txt","r");
    

    第 2 个参数的几种形式(打开文件的方式):


    • b 是二进制模式的意思,b 只是在 Windows 有效,在 Linux 用 r 和 rb 的结果是一样的
    • Unix 和 Linux 下所有的文本文件行都是 结尾,而 Windows 所有的文本文件行都是 结尾
    • 在 Windows 平台下,以 "文本" 方式打开文件,不加 b:
      • 当读取文件的时候,系统会将所有的 转换成
      • 当写入文件的时候,系统会将 转换成 写入
      • 以 "二进制" 方式打开文件,则读写 都不会进行这样的转换
    • 在 Unix/Linux 平台下,"文本" 与 "二进制" 模式没有区别, 作为两个字符原样输入输出

    文件的关闭

    任何文件在使用后应该关闭:

    • 打开的文件会占用内存资源,如果总是打开不关闭,会消耗很多内存。
    • 一个进程同时打开的文件数是有限制的,超过最大同时打开文件数,再次调用 fopen 打开文件会失败。
    • 如果没有明确的调用 fclose 关闭打开的文件,那么程序在退出的时候,操作系统会统一关闭。

    举例:

    # include <stdio.h>
    # include <stdlib.h>
    
    int main(void) {
        FILE* fp;
        // 打开文件
        fp = fopen("abc.txt", "r");
    
        // 对打开的文件进行判断
        if (fp == NULL) {
            printf("打开文件失败!");
            return -1;
        }
        printf("文件打开成功:%p
    ", fp);
    
        // 关闭文件
        fclose(fp);
        return 0;
    }
    

    判断文件结尾

    在 C 语言中,EOF 表示文件结束符(end of file)。在 while 循环中以 EOF 作为文件结束标志,这种以 EOF 作为文件结束标志的文件,必须是文本文件。在文本文件中,数据都是以字符的 ASCII 代码值的形式存放。我们知道,ASCII 代码值的范围是 0~127,不可能出现 -1,因此可以用 EOF 作为文件结束标志。

    #define EOF (-1)
    

    当把数据以二进制形式存放到文件中时,就会有 -1 值的出现,因此不能采用 EOF 作为二进制文件的结束标志。为解决这一个问题,ANSIC 提供一个 feof 函数,用来判断文件是否结束。feof 函数既可用以判断二进制文件又可用以判断文本文件。

    文件的读写

    按照字符读写文件

    # include <stdio.h>
    # include <stdlib.h>
    
    int main(void) {
        int len;
        char ch;
        FILE* fp;
    
        // 打开文件
        fp = fopen("abc.txt", "r");
    
        // 对打开的文件进行判断
        if (fp == NULL) {
            printf("打开文件失败!");
            return -1;
        }
    
        // 读出文件1
        while((ch=fgetc(fp)) != EOF)
            printf("%c", ch);
    
        // 读出文件2
        while (!feof(fp)) {
            ch = fgetc(fp);
            printf("%c", ch);
        }
    
        // 关闭文件
        fclose(fp);
        return 0;
    }
    

    # include <stdio.h>
    # include <stdlib.h>
    # include <string.h>
    
    int main(void) {
        int i, len;
        char ch;
        char str[] = "What your mean?";
        len = strlen(str);
        FILE* fp;
        // 打开文件
        fp = fopen("abc.txt", "w");
    
        // 对打开的文件进行判断
        if (fp == NULL) {
            printf("打开文件失败!");
            return -1;
        }
    
        // 写入文件
        for(i=0; i < len; i++) {
            ch = fputc(str[i], fp);
            printf("%c", ch);
        }
    
        // 关闭文件
        fclose(fp);
        return 0;
    }
    

    按照行读写文件

    文件的定位

  • 相关阅读:
    CSS
    人物
    CSS
    CSS
    概念- 工业4.0
    C#正则表达式
    六月定律
    c#中实现登陆窗口(无需隐藏)
    c#中关于String、string,Object、object,Int32、int
    一个快速找第k+1小的算法
  • 原文地址:https://www.cnblogs.com/liujiaqi1101/p/13724199.html
Copyright © 2020-2023  润新知