结构体
- 计算机可以解决现实中可被计算的问题,首先要对现实问题进行数据表示
- 要存储某种信息,用相应类型定义相应变量,变量里面去存储值
- 只要是变量,就在内存中占据一定大小的空间
- 结构体是一种更为复杂的类型,结构体中包含了各种类型的字段,这些字段是对这个更为复杂的类型的解析
- 定义结构体变量
C语言中用struct+结构体名
C++中不加struct关键字,因为C++中struct是类 - 访问结构体成员有2种方法:
.直接引用
->间接访问
结构体内存对齐原则
- 开辟空间时按字节数最大的基础数据类型进行对齐
- 由于对齐原则,结构体中字段的类型和个数一致时,占用内存大小与顺序相关
- 节省内存方式有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;
}
- 强制转换空地址 NULL,无需定义变量
- 求结构体各字段偏移量,offset宏里定义temp变量求得
#define offset(T, a) ({\
T temp;\
(char *)(&temp.a) - (char *)(&temp);\
})
共用体
- 又称联合体,也是定义一种更为复杂的类型,关键字struct换成union
只要是变量,就一定会占相应的空间大小
- 共用体,共用的是同一片空间 ,空间大小是所占空间最大的字段的大小
- 上图共用体有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;
}
-
为什么IP能转整数?
IP形式:XXX.XXX.XXX.XXX,XXX范围是0-255(255是8个1),需要8个二进制位(1字节)表示,且为无符号类型。4个XXX是4个字节,所以一个IP可以对应一个无符号整型的值 -
有格式的字符串的剪裁用sscanf(),拼接用sprintf()
大、小端机
小端机,数字低位-----低地址位
大端机,数字低位-----高地址位
- 低位赋值到低地址(注意)
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];
-
int型赋值给char型?
0-255整数在小端机中都在0号字节存储,直接赋值给char类型数组没有问题 -
输入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];
-
对于大小端机不确定的问题,将本机字节序转成网络字节序(有统一标准,对方下载时自动转成对应的字节序)
-
判断小端机
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位
指针与地址
-
变量的性质:
变量作用是存储值;
变量有空间大小;
变量是有地址的(地址按字节编号,取地址符&默认取编号最小的地址) -
64位、32位操作系统的位是指什么?
内存中的地址是用64/32bits进行编址,或者说是地址的寻址范围
32位操作系统(0-----232-1)只能识别4GB(2的32次个地址)的内存,再加内存条也识别不了
- 用指针变量存储和表示地址(C语言中0X开头)
- 无论指向什么类型,指针类型所占空间大小一样(区分类型的目的是±n操作)
64位操作系统指针占8字节(64bits表示1个地址),32位操作系统占4字节 - 不同类型指针可以传值
int *p;
char *q;
p可以传值给q - int *p;
*跟着p走,p是指针变量,它自己代表了变量的内容(地址)
*p取值/取反操作 - 指针变量也是变量,是变量就能存储值,是变量就有地址,存储指针变量地址的变量,称为指针的指针
- scanf()理解
scanf给变量写入值,传的是变量地址(传变量也不会报错),这不是scanf函数定义要求的,而是我们的功能逻辑需要这样做
函数调用时值传递,作用域在函数内部,&将外部参数的地址当做变量传进去,scanf写值时写在了地址对应的内存里,在函数外部访问时值仍然存在 - 重要概念
- p+1是向后跳跃了元素类型的字节数(区分指针类型的原因也是因为涉及到±1的操作),等价于&p[1],p是数组可以这么用
- *p=a,所以第三个等式成立
函数指针
-
数组中讲过,把函数当作参数传来传去,需要定义存储函数信息的变量,我们称为函数指针变量
-
定义变量:类型名+变量名
-
定义函数指针变量
int (*add)(int, int);函数指针
int *add(int, int);函数声明,返回值类型int * -
typedef将变量提升至类型
add由函数指针变量提升为函数指针类型
typedef 的用法
- 结构体类型、结构体指针类型重命名
- 函数指针命名:
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;
}
- g++ -E 25.pointer.cpp查看预编译后的代码,p2类型是char,不是char *
- define 简单的符号替换(p1类型是char *,p2类型是char)
typedef 重命名(p3,p4类型都是char *)
main 函数
-
操作系统调用main函数,操作系统传参(有参时),返回值给操作系统
int main(); 函数最后 return 0;的0值返回给操作系统 -
linux操作系统,$?判断操作系统执行的上一条命令成功没有,返回0成功,非0值不成功
-
int argc 命令行的参数个数
char *argv[ ],命令行参数的字符串形式
argv 是数组,数组中每一位是 char *
等价于 char **argv
*等价于[ ],所以也等价于char[ ][ ],一维数组是一个字符串,二维数组是一堆字符串 -
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;
}
- argc和argv
- env
- 利用env中 "USER=***"做程序指纹识别