[C] 6. 复杂结构与指针

结构体

  • 计算机可以解决现实中可被计算的问题,首先要对现实问题进行数据表示
  • 要存储某种信息,用相应类型定义相应变量,变量里面去存储值
  • 只要是变量,就在内存中占据一定大小的空间

  • 结构体是一种更为复杂的类型,结构体中包含了各种类型的字段,这些字段是对这个更为复杂的类型的解析
  1. 定义结构体变量
    C语言中用struct+结构体名
    C++中不加struct关键字,因为C++中struct是类
  2. 访问结构体成员有2种方法:
    .直接引用
    ->间接访问

结构体内存对齐原则

  1. 开辟空间时按字节数最大的基础数据类型进行对齐
  2. 由于对齐原则,结构体中字段的类型和个数一致时,占用内存大小与顺序相关
  3. 节省内存方式有2种:
    1. 同类型字段定义时顺序放在一起
    2. #pragma pack(n)改变对齐方式,pragma是预定义的宏
#pragma pack(1)
typedef struct NODE {
  short a;
  int b;
  char c;
} NODE;

/*-----   注释    -----*/
1.默认是以结构体中所占字节数最大的数据类型为对齐标准,sizeof(NODE)=12
2.#pragma pack(1),取消对齐,1B对齐,sizeof(NODE)=7
  编译器从“n”和”结构体中最长的数据类型长度“中选较小的那个作为对齐标准

求结构体偏移量-25.pointer.cpp

#include <stdio.h>
#define offset(T, a) (long)(&(((T *)(NULL))->a)) // long使用%ld

struct Data {
    int a;
    double b;
    char c;
};

int main() {
    int num = 0x0626364; // 小端机 :64 63 62 0
    printf("%s\n", (char *)(&num)); // 'd' 'c' 'b' '\0'
    printf("%ld\n", offset(struct Data, a)); // long使用%ld
    printf("%ld\n", offset(struct Data, b));
    printf("%ld\n", offset(struct Data, c));
    return 0;
}
  1. 强制转换空地址 NULL,无需定义变量
  2. 求结构体各字段偏移量,offset宏里定义temp变量求得
#define offset(T, a) ({\
    T temp;\
    (char *)(&temp.a) - (char *)(&temp);\
})

共用体

  • 又称联合体,也是定义一种更为复杂的类型,关键字struct换成union

只要是变量,就一定会占相应的空间大小

  • 共用体,共用的是同一片空间 ,空间大小是所占空间最大的字段的大小
  1. 上图共用体有2个字段:一个结构体,一个无符号整型
  2. 使用匿名结构体,struct {…} bytes;少了结构体名
    匿名结构体是合法的,但是只能在定义时初始化一次,后面就找不到了


习题3.使用共用体实现 ip 转整数的功能

#include <stdio.h>

union IP {
    struct {
        unsigned char a1;
        unsigned char a2;
        unsigned char a3;
        unsigned char a4;
    } ip;//匿名结构体
    unsigned int num;
};

int main() {
    union IP p;
    char str[100];//ip以字符串的形式读入
    int arr[4];
    while (~scanf("%s", str)) {
        //把"192.168.0.1"中数字取出来
        sscanf(str, "%d.%d.%d.%d", arr, arr + 1, arr + 2, arr + 3);
        //小端机:数字低位—>低地址
        p.ip.a1 = arr[3];
        p.ip.a2 = arr[2];
        p.ip.a3 = arr[1];
        p.ip.a4 = arr[0];
    	printf("%u\n", p.num);//无符号整型%u
    }
    return 0;
}
  1. 为什么IP能转整数?
    IP形式:XXX.XXX.XXX.XXX,XXX范围是0-255(255是8个1),需要8个二进制位(1字节)表示,且为无符号类型。4个XXX是4个字节,所以一个IP可以对应一个无符号整型的值

  2. 有格式的字符串的剪裁用sscanf(),拼接用sprintf()


大、小端机

小端机,数字低位-----低地址位
大端机,数字低位-----高地址位

  1. 低位赋值到低地址(注意)

    192.168.0.1低位是1,字符串输入时是p.ip.a4,放到低地址arr[0]
//正确赋值
p.ip.a1 = arr[3];
p.ip.a2 = arr[2];
p.ip.a3 = arr[1];
p.ip.a4 = arr[0];
  1. int型赋值给char型?
    0-255整数在小端机中都在0号字节存储,直接赋值给char类型数组没有问题

  2. 输入IP为192.168.0.1与192.168.0.2之间相差了1,但是它们映射得到的整型结果并不是相差1,是因为错误赋值了

//错误赋值
p.ip.a1 = arr[0];
p.ip.a2 = arr[1];
p.ip.a3 = arr[2];
p.ip.a4 = arr[3];
  1. 对于大小端机不确定的问题,将本机字节序转成网络字节序(有统一标准,对方下载时自动转成对应的字节序)

  2. 判断小端机

int is_little() {
    static int num = 1;//静态变量static
    //num看成拥有4个字节的字符数组
    return ((char *)(&num))[0];//&num后强转char *,取0位
}
printf("%d\n", is_little());
  • int型整数1占4个字节,1存在低地址(0号字节上)
  • num看成拥有4个字节的字符数组,&num后强转为char *,取0位

指针与地址

  1. 变量的性质:
    变量作用是存储值;
    变量有空间大小;
    变量是有地址的(地址按字节编号,取地址符&默认取编号最小的地址)

  2. 64位、32位操作系统的位是指什么?
    内存中的地址是用64/32bits进行编址,或者说是地址的寻址范围
    32位操作系统(0-----232-1)只能识别4GB(2的32次个地址)的内存,再加内存条也识别不了


  1. 用指针变量存储和表示地址(C语言中0X开头)
  2. 无论指向什么类型,指针类型所占空间大小一样(区分类型的目的是±n操作)
    64位操作系统指针占8字节(64bits表示1个地址),32位操作系统占4字节
  3. 不同类型指针可以传值
    int *p;
    char *q;
    p可以传值给q
  4. int *p;
    *跟着p走,p是指针变量,它自己代表了变量的内容(地址)
    *p取值/取反操作
  5. 指针变量也是变量,是变量就能存储值,是变量就有地址,存储指针变量地址的变量,称为指针的指针
  6. scanf()理解
    scanf给变量写入值,传的是变量地址(传变量也不会报错),这不是scanf函数定义要求的,而是我们的功能逻辑需要这样做
    函数调用时值传递,作用域在函数内部,&将外部参数的地址当做变量传进去,scanf写值时写在了地址对应的内存里,在函数外部访问时值仍然存在
  7. 重要概念
  • p+1是向后跳跃了元素类型的字节数(区分指针类型的原因也是因为涉及到±1的操作),等价于&p[1],p是数组可以这么用
  • *p=a,所以第三个等式成立

函数指针

  • 数组中讲过,把函数当作参数传来传去,需要定义存储函数信息的变量,我们称为函数指针变量

  • 定义变量:类型名+变量名

  1. 定义函数指针变量
    int (*add)(int, int);函数指针
    int *add(int, int);函数声明,返回值类型int *

  2. typedef将变量提升至类型
    add由函数指针变量提升为函数指针类型

typedef 的用法

  1. 结构体类型、结构体指针类型重命名
  2. 函数指针命名:
    func是函数指针变量,提升为函数指针类型,可以func a;定义a为函数指针变量

define、typedef区别-25.pointer.cpp

#include <stdio.h>

// define/typedef的区别
#define ppchar char *
typedef char * pchar;

int main() {  
    ppchar p1, p2;
    pchar p3, p4;
    printf("p1 = %lu, p2 = %lu\n", sizeof(p1), sizeof(p2));
    printf("p3 = %lu, p4 = %lu\n", sizeof(p3), sizeof(p4));
    return 0;
}
  1. g++ -E 25.pointer.cpp查看预编译后的代码,p2类型是char,不是char *
  2. define 简单的符号替换(p1类型是char *,p2类型是char)
    typedef 重命名(p3,p4类型都是char *)

main 函数

  1. 操作系统调用main函数,操作系统传参(有参时),返回值给操作系统
    int main(); 函数最后 return 0;的0值返回给操作系统

  2. linux操作系统,$?判断操作系统执行的上一条命令成功没有,返回0成功,非0值不成功

  3. int argc 命令行的参数个数
    char *argv[ ],命令行参数的字符串形式

    argv 是数组,数组中每一位是 char *
    等价于 char **argv
    *等价于[ ],所以也等价于char[ ][ ],一维数组是一个字符串,二维数组是一堆字符串

  4. char **env 环境变量

main参数讲解-26.main.cpp

#include <stdio.h>
#include <string.h> // strncmp和strcmp
#include <stdlib.h> // exit(0)

void output(int argc, char *argv[], char *env[]) {
    printf("argc = %d\n", argc);
    for (int i = 0; i < argc; i++) {
        printf("argv[%d] = %s\n", i, argv[i]);
    }
    for (int i = 0; env[i]; i++) {
        if (!strncmp(env[i], "USER=", 5)) { // 某个env[i]前5位"USER="
            if (!strcmp(env[i] + 5, "xzh")) { // 用户名"xzh"(自定义), 程序指纹识别功能
                printf("Welcome capatain Xu!\n");
            } else {
                printf("you are not USER! please gun!\n");
                exit(0);
            }
        }
        // printf("env[%d] = %s\n", i, env[i]); // 打印env
    }
    return ;
}

int main(int argc, char *argv[], char **env) {
    output(argc, argv, env);
    return 0;
}
  1. argc和argv
  2. env
  3. 利用env中 "USER=***"做程序指纹识别
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值