类型转换
1.隐式转换
image.png
2.显示转换/强制类型转换
浮点型强制转换成int 直接把小数点去掉,保留整数部分
int main(void) {
printf("%lf,%d\n", 12 + 12.2, 12 + (int) 12.2);//24.200000,24
return 0;
}
强转指针类型/强转地址类型
指针/地址的类型决定着指针/地址的读写方式(字节数)
int main(void) {
//正常是操作四字节的内存,强转之后p就操作8字节的内存,所以就越界了
//把小的转成大的就越界
int a = 12;
double *p = (double *) &a;//❌,导致越界
*p = 24;
//正常是操作8字节的内存,强转之后操作4字节的内存,操作上不会报错,但是值会有误。
double b = 12;
int *p1 = (int *) &b;
*p = 12;
//因为int是四字节,float也是四字节,不越界
int c = 12;
float *p2 = (float *) &c;
*p2 = 12.3f;
//double是8字节的,强转成4字节的
double d = 12;
int *p3 = (int *) &d;
*p = 12;//操作的是前四个字节的内存
*(p + 1) = 23;//操作的是后面四个字节的内存
//那么,怎么操作8字节的中间四个字节呢❓
double e = 12;
int *p4 = (short *) &e;
//p4转换成short类型的指针,这种操作是一个字节(转成char *也行)
//+2:指向第3个字节。 (short *)p4+2
// (int *) 转成int * 就开始操作4字节的内存啦
// * 赋值
*(int *) ((short *) p4 + 2) = 23;
return 0;
}
大小端存储
字节对齐决定数据在内存中存储的起始地址。
大小端存储决定数据在内存中存储的顺序(计算机存储数据的方式)
小端存储:数据的高位存在内存的高位(高位地址)
image.png大端存储:数据的高位存在内存的低位(低位地址)
image.png如何测试系统是大段存储还是小端存储
方法1:强转、方法2:联合
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
union UN {
int i;
char c[4];
} u = {134480385};
int main(void) {
//方法一
int a = 134480385;//二进制是:1000 100 10 1
char *p = (char *) &a;
for (int i = 0; i < 4; ++i) {
//0x7ffee4082938,1
//0x7ffee4082939,2
//0x7ffee408293a,4
//0x7ffee408293b,8
//因为0x7ffee4082938最小,小地址存的1,而1是a的数据低位,所以是小端存储
printf("%p,%d\n", &p[i], p[i]);
}
//方法二 联合测试
for (int i = 0; i < 4; ++i) {
//0x10933b018,1
//0x10933b019,2
//0x10933b01a,4
//0x10933b01b,8
//也是小端存储,原理其实是一样的,就是能一次可以读取一个字节就可以了
printf("%p,%d\n", &u.c[i], u.c[i]);
}
return 0;
}
类型重命名 typedef
重命名之后,其实是一模一样的。
作用:
1.增加代码的可读性
2.使用起来更方便
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef int myInt;
typedef myInt hisInt;
typedef unsigned int unint;
int main(void) {
typedef myInt herInt;//也可以写在里面
myInt a = 1;
hisInt b = 2;
printf("%d,%d", a, b);//1,2
return 0;
}
typedef int myInt;
typedef int myInt;//相同类型,名字可以重复
//typedef double myInt;//不同类型不可以 ❌
int main(void) {
typedef myInt herInt;
myInt a = 1;
printf("%d", a);//1,2
return 0;
}
指针重命名
typedef int *pint; //int * 指针重命名成pint
int main(void) {
pint p = NULL;
return 0;
}
结构体重命名
//方法一
typedef struct stru {
int a;
} _Node;//请注意:如果重命名,这个地方就不能定义变量了
//方法二
typedef struct stru node;
//结构体更适合无名结构体,这样可以随心所欲的定义结构体
//如果没有重命名,只能在外面声明
typedef struct {
int a;
} Name;//请注意:如果重命名,这个地方就不能定义变量了
int main(void) {
_Node s;
node n;
Name name1;
return 0;
}
函数体重命名
void fun(int a, double b) {
printf("%d,%lf", a, b);
}
//这是函数指针类型void (*)(int , double)
typedef void (*pFun)(int, double);
int main(void) {
void (*p)(int, double) =fun;//定义一个函数指针
void (*p1)(int, double) =fun;
pFun p3 = fun;//使用重命名 定义一个函数指针
p3(2, 1.3);//函数调用就是函数地址加上参数 //2,1.300000
return 0;
}
宏 #define
enum是给整数命名,typedef是给类型命名,那么宏可以给一切“重命名”
本质就是:单纯的替换
define:预处理指令
常量宏
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define ONE 1
#define ONE 1
int main(void) {
//ONE 会被替换成1,在计算机眼里,没有ONE,只要1
//宏后面不能加分号,要不然,会把ONE看成1;
//宏可以重复,最后那个起作用
printf("%d", ONE);
return 0;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define ONE 1+2*3
int main(void) {
//宏不进行运算,只是替换的作用
//相当于printf("%d", 1+2*3)
printf("%d", ONE);//
return 0;
}
宏是可以相互使用的,但是注意前后顺序,按照正常逻辑去写
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define ONE 1
#define TWO 2
#define TWOTWO TWO //宏可以这样写
#define PRINTF printf("%d,%d\n",ONE,TWO);//宏是可以相互使用的
#define PRINTF2 printf("%d,%d\n",
int main(void) {
//实际就是:printf("%d,%d\n",1,2)
PRINTF //1,2
PRINTF2 ONE,TWO);//1,2
return 0;
}
define ONE
空宏也可以,相当于定义了变量未初始化,但宏不是变量,可以说是个符号,替代
#define ONE //空宏也可以
int main(void) {
ONE
return 0;
}
宏的注意点
宏的表达式一般要加小括号 #define TWO (1+1)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define ONE 1+1
#define TWO (1+1)
int main(void) {
//单纯的替换:2*1+1=3
printf("%d\n", 2 * ONE);//3
//表达式一般要加小括号
printf("%d\n", 2 * TWO);//4
return 0;
}
参数宏
参数宏没有类型,只是替换,但是替换后语法要正确哦
//宏没有类型,只是替换
#define PRINTF(x) printf("%d\n",x);
int main(void) {
//12传给x,x又传给printf的x
PRINTF(12)//12
//printf("%d\n",12*12);
PRINTF(12*12)//144
return 0;
}
参数宏也要加小括号 #define PRINTF(x) printf("%d\n",(x)*2);
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//宏没有类型,只是替换
//参数宏一般也要加小括号
#define PRINTF(x) printf("%d\n",(x)*2);
#define PRINTF2(x) printf("%d\n",x*2);
int main(void) {
//12传给x,x又传给printf的x
PRINTF(2+2)//8
PRINTF2(2+2)//6
return 0;
}
多个参数的宏
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//宏没有类型,只是替换
//参数宏也要加小括号
#define PRINTF(x, y) printf("%d\n",(x)*(y));
int main(void) {
PRINTF(2 + 2, 3 + 3)//24
return 0;
}
标准宏要这么写 #define SUM(x, y) ((x)+(y)) //表达式和参数都加小括号哦
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//表达式和参数都加小括号哦
//标准宏要这么写
#define SUM(x, y) ((x)+(y))
#define SUM2(x, y) (x)+(y)
int main(void) {
printf("%d\n", SUM(3, 4) * 3);//21
printf("%d\n", SUM2(3, 4) * 3);//15
return 0;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//比较大小
#define MYMAX(x, y) ((x)>(y)?(x):(y))
int main(void) {
printf("%d\n", MYMAX(5, 4));
return 0;
}
宏的指示符 \、# 和 ##
- \ :拼接
注意:\后面什么都不能有,空格也不能有
#define SUM() 1+2\
+3+4
#define PRI printf("aaa");\
printf("bbb");\
printf("ccc");
int main(void) {
printf("%d\n", SUM());//10
PRI //aaabbbccc
return 0;
}
2.#(x) 字符串指示符
不管x是什么,#之后就是个字符串
相当于给x的参数加双引号
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define SUM(x) #x
int main(void) {
//替换成字符串aaaa
printf("%s\n", SUM(aaaa));//aaaa
//替换成字符串"aaaa"
printf("%s\n", SUM("aaaa"));//"aaaa"
return 0;
}
3.##:字符串拼接
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//#x 转成字符串,## 拼接在一切
#define ONE(x, y) #x ## #y ## "feh"
int main(void) {
printf("%s\n", ONE("123", "4"));
return 0;
}
网友评论