第一节 地址运算符、间接运算符、指针初识
&
地址运算符 由地址运算符所组成的表达式称为地址表达式*
间接运算符 由间接运算符所组成的表达式称为间接表达式- 共同特点:
- 一元运算符,只需要一个右操作数
- 优先级相同,从右向左结合
int main(){
int a = 100;
&a; //地址 --> 指针
*&a; // *地址 --> 指针所指向的值
int b = 100, c = 100;
b + c; //表达式的值为200,是一个"int"类型的整数
&b; //表达式的值为(int*)0x22fe44,是一个"指向int"类型的指针
/*
long int b = 100, c = 100;
&b; //一个指向“long int”类型的指针
*/
return 0;
}
&a;
: 地址表达式,变量a的内存地址为地址表达式的值,地址又被称作“指针”
*&a;
:间接表达式,地址所指向的值(100)为间接表达式的值,地址所指向的值又被称为“指针所指向的值”
- 间接表达式的运算顺序为从右向左,在此表达式里,地址运算符的优先级高于间接运算符
第二节 声明存储指针的变量–整数
-
如何声明一个指针类型的变量?
- 在声明的变量前加一个
*
- 在声明的变量前加一个
-
int main(){ int a; //变量a,用来存储"int类型"的整数 int * b; //变量b,声明用来存储"指向int类型"的指针 b = &a; //将指向"int类型"的指针赋值给b *b = 100; /* *b为间接运算符,优先级高于赋值运算符 先将变量b进行左值转换,*b的值则为变量a的值 */ return 0; }
-
int * b; //变量b,用来存储"指向int类型"的指针
b = &a; //将指向"int类型"的指针赋值给b,由于上面一行代码声明了变量b为指针类型,故此行代码才能正确运行 -
*
: 根据地址对变量进行还原,*b = = a
*b
的值等于变量a的值*b = 100;
< == >a = 100;
- 通过
*
运算符和变量b,间接影响变量a的值,所以将*
运算符称为间接运算符 *&a
: 先执行&a
取出地址(指针),再执行*&a
对取出的指针进行还原*&a < == > a
第三节 函数名-指针转换、声明存储指针的变量–函数、使用变量调用函数
void swap(int * a, int * b){
int temp = * b;
* b = * a;
* a = temp;
}
int main(void){
int m = 10086, n = 10010;
swap(& m, & n);
void (* pf)(int *, int *) = swap;
pf(& m, & n); //处理器在进行处理的时候,会先将函数调用表达式`pf()中存储的是一个指向swap的指针(地址)`进行一个左值转换,通过变量pf调用swap函数
//(*pf)(& m, & n); (&*pf)(& m, & n); (*&*pf)(& m, & n); (&*&*pf)(& m, & n);
return 0;
}
- 在main函数中,函数名(swap)会被隐式的转换为函数的地址(“指向函数的指针”),处理器会根据地址定位到swap函数所在的位置并执行,
swap (& m, & n) < == > (&swap)(& m, & n);
- 声明pf变量,用于存储“指向函数swap”的指针,只要符合声明格式的函数,pf都可以进行存储
第四节 类型匹配、数值的类型、整型常量的:前缀、后缀、类型判定方法
int main()
{
//“变量”在声明的时候,我们规定了它的类型
int a; //变量a为int类型
//在给变量进行赋值的时候,必须赋值给它相对应类型的数值
a = 1.5; //类型不匹配,存储就会出现问题
//“常量”也是有类型的
//整数类型的常量 --> 简称整型常量
//1.整型常量3种进制的写法(前缀)
a = 125; //十进制写法:以非0的数字开头
a = 0125; //八进制写法:以0开头
a = 0x125; //十六进制写法:以0x/0X开头
a = 0b00001; //二进制写法:以0b/0B开头
//注意”C语言并没有规定二进制的写法,这是编译器自行添加的
//所以,更换编译器的时候,程序有可能会报错
//2.整型常量的3个后缀:u(unsigned)、l(long )、ll(long long)(不分大小写)
a = 125u;
a = 125L;
a = 125LL;
a = 125ul; //u和l:谁前睡后都可以
a = 125ull; //u和ll:谁前谁后都可以
//怎样判定常量是什么类型?
//前缀 + 后缀 + 具体的C实现 这三个因素决定了一个常量的类型
//具体判定方法:C语言提供了整型常量类型对照表,详见下表
return 0;
}
- 整型常量对照表
第五节 整数类型转换为_Bool类型、隐式类型转换
int main(void){
_Bool a, b, c, d;
a = 0; //int类型的0 ----> _Bool类型的0
b = 1; //int类型的1 ----> _Bool类型的1
c = 100; //int类型的100 ----> _Bool类型的1
d = 100000000000; //long long int类型的100000000000 --> _Bool类型的1
return 0;
}
- 由于a,b,c,d都是int或long long int型的数据,与_Bool类型不一致,故需要进行类型转换。
- 类型转换不需要人为进行参与,自动进行,所以也称为隐式类型转换,转换的结果为上述代码段中的注释
- 将“整数类型”转换为“_Bool类型”时:
- 如果数值在_Bool类型存储范围以内,只有类型会发生改变,数值将不会发生改变
- 如果数值不在_Bool类型存储范围以内,类型和数值将全部发生改变,数值将被转换为1
第六节 整数类型转换为非_Bool整数类型、隐式类型转换
int main(void){
short int a = 100; //将int类型的100 --> 隐式转换为short int类型的100
int b = 3700u; //将unsigned int类型的3700 ----> 隐式转换为int类型的3700
//由于a和b的类型不一致,需要进行隐式类型转换
//新类型为无符号整数
unsigned char c = -1; //int类型的-1隐式转换为unsigned char类型的255
//由于-1太小,不在新类型的存储范围之内,所以需要加上一个数(256)得到一个新值
//256由来:新类型(char)的最大存储值为1个字节(255),进行加1 ----> -1 + 256 = 255
unsigned char d = 257; //将int类型的257隐式转换为unsigned char类型的1
//由于257太大,不在新类型的存储范围之内,所以需要减去一个数(256)得到一个新值
//256由来:新类型(char)的最大存储值为1个字节(255),进行加1后得到一个新值,做减法运算 ----> 257 - 256 = 1
//有符号整数 --> 不同的C实现会有不同的处理结果
return 0;
}
- 由于类型的不一致,需要进行隐式类型转换
- 将“整数类型”转换为“非_Bool整数类型”时:
- 如果数值在新类型的存储范围以内,只有类型会发生改变,数值将不会发生改变
- 如果数值不在新类型的存储范围之内,类型和数值都将全部发生改变
- 数值的改变分为两种情况:
- 新类型是无符号整数、新类型是有符号整数
第七节 表达式值的类型
int max(int a, int b)
{
if(a >= b)
//关系表达式的类型 --> int(固定)
return a;
return b;
}
int main(void)
{
//每个表达式都有一个值,这个值也是有类型的
//"表达式值的类型",简称"表达式的类型"
int x = 1, y = 1;
unsigned char z;
x += z = 56;
//整型常量表达式:56 --> int类型(判断方法见本章第三节)
//赋值表达式:z = 56: 值 -> 56、类型 -> unsigned char
//复合表达式的值: x += z = 56:值 -> 57、类型 -> int
//赋值表达式 --> 值 -> 左操作数的值、类型 -> 左操作数的类型
z += max(x, y);
//函数调用表达式:max(x, y):值 -> 函数的返回值、类型 -> 函数的返回类型
//复合赋值表达式: z += max(x, y):类型 -> unsigned char
y = ++ x;
//前缀递增表达式: ++ x:类型 -> int -> 操作数的原型
//赋值表达式: y = ++ x: 类型 -> int
return 0;
}
第八节 隐式类型转换和运算符的关系、整型转换阶、整型提升
int main(void)
{
//类型的转换是由云端夫来发起的
//赋值运算符:将右操作数转换为和左操作数一样的类型
singned char a = 1, b = 2;
//递增运算符:不改变操作数的类型
++ a;
/*
其他的大多数运算符都需要将操作数转换为相同的类型,然后再进行运算
具体的转换规则:以加性运算符为例
*/
a + 380L;
//第一步:整型提升 --> 将转换阶低于int或者unsigned int的类型转换为int或者unsigned int类型
//第二步:整型提升后,如果操作数类型不一致 --> 将转换阶低的操作数转换为和转换阶高的操作数一样的类型
//详细转换见整型的转换阶
/*
注意:
1.整型的提升是对第一步的描述,和第二步无关
2.整型提升以int类型为首选
3.当进行整型提升时,如果数值不在int类型的存储范围之内,那么就将它转换为unsigned int --> 当short int 和 int的存储范围相同时
*/
a + b;
//整型提升,只要在int或unsigned int类型以下的,都需要进行整型提升
int c = -1;
unsigned int d = 100;
c + d;
//整型提升后,两个类型在同一阶
//将“有符号类型”转换为“无符号类型”
return 0;
}
- 整型的转换阶
第九节 负号运算符、负号表达式
int main(void)
{
unsigned char a;
a = -1; //1为int型,4个字节"0000 0000 0000 0000 0000 0000 0000 0001",在此处,a的值为255
// - : 负号运算符 --> 负号表达式 --> 在C语言中,负数是通过运算所得到的
// 一元运算符,只需要一个右操作数
//运算规则:
// 1.如果操作数是一个整数,负号运算符会发起类型转换 --> 对操作数进行整型提升
// 2.对操作数进行“取补码”操作,得到 "1111 1111 1111 1111 1111 1111 1111 1111"
signed char b = - a ++; //b的值为1
return 0;
}
第十节 显式类型转换、转型运算符、转型表达式、()的多种用法
int main(void)
{
//显式/强制类型转换
//语法规则:(类型名)表达式
//() --> 转型运算符 --> 将表达式的类型转换为括号中所指定的类型
long long a;
a = 33; //处理器在执行运算时会先进行隐式的运算,将int型的33转换成long long int型的33并赋值给a
a = (long long int)33; //int --> long long int
a = (long long)33 + 100;
a = (long long)(33 + 100); //(33 + 100) --> 基本表达式
//()运算符的三种用法
//1.函数调用运算符
//2.转型运算符
//3.构成基本表达式
return 0;
}
第十一节 整数-指针的转换、间接运算符的性质、再识指针
int main(void)
{
//间接运算符 --> 根据指针(地址)对变量进行还原 --> 它的操作数必须是指针,并且它不会发起类型转换
int a = 0;
int b = (int) &a;//&a的值为变量a的地址,类型为指向int类型的指针;此行代码的作用是将变量a的地址存储到变量b中
/*
错误:间接运算符的操作数类型错误
* b = 100;
*/
*(int *)b = 100; //(int *)b先将b进行左值转换,此时b中存储的是a的地址,类型为指向int类型的指针
//此行代码执行完以后,a的值变为100
//*(int *)b < == > a
return 0;
}
第十二节 研究指针三剑客、类型的本质
int main(void)
{
int a = 0; //假设:变量a的内存地址为123456
&a;
int b = (int)&a;
return 0;
}
&a
: 值为123456,类型为指向int类型的指针- b : 值为123456,类型为int整型
&a和变量b
: 数值相同,类型不同- 分为不同类型的原因:
- 不同类型,对应着不同的使用规则
* b
----> 编译器会报错* &a
----> 将&a
的值解析为地址,然后访问这个地址所对应的内存单元- C语言为什么要引入
地址运算符&
、间接运算符*
、指针
这几种类型?- 让它们互相配合,使操作者可以通过地址来访问内存
&
: 获取一个数值,并将这个数值的类型规定为指针*
: 通过指针类型的数值,来访问内存单元
- 汇编语言可以通过地址来任意访问内存 ----> 强大
- 高级语言把内存访问的细节屏蔽掉 ----> 简单
- C语言 ----> 既强大又简单
第十三节 指针-指针转换、复杂表达式分析
int abc(int a, int b)
{
return 0;
}
int main(void)
{
int a, (* b)(int, int) = abc;
// 变量 ----> 存储数据
//变量a ----> int整型
//变量b ----> 指针 ----> 指向函数 ----> 两个int类型的参数,一个int类型的返回值
/*
abc ----> 函数名 ----> 函数名-指针转换 ----> 值:函数abc的地址 ----> 类型 ----> 指针 ----> 指向函数 ----> 两个int类型的参数,一个int类型的返回值
*/
//变量b中的值为多少? ----> 函数abc在内存中的地址
//变量b中的值是什么类型? ----> int (*) (int , int)
void (* c)(void) = (void (*) (void)) b;
//变量 ----> 存储数据
//变量c ----> 指针 ----> 指向函数 ----> 没有返回值,没有参数
//b ----> 变量 ----> 左值转换 ----> 值:函数abc的地址、类型:int (*) (int, int)
//变量c中的值是多少? ----> 函数abc在内存中的地址
//变量c中的值是什么类型? ----> void (*) (void)
a = ((int (*)(int , int )) c)(1,2);//函数调用表达式,调用函数abc,并传入值1和2
//((int (*)(int , int )) c)最外层()是为了让基本表达式作为一个整体(转型运算符)参与运算
//基本表达式:(int (*)(int , int )) c
//处理器会将c的值进行左值转换,将c的原类型void (*) (void)强制转换为int (*)(int , int )
return 0;
}
第十四节 从内存的角度搞定 – 多级指针
int main(void)
{
int i, * pi = &i, * * ppi = & pi;
* * ppi = 1;
* ppi = 1;
//--------------------------------------------
int j;
pi = & j;
** ppi = 3;
return 0;
}