IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【C语言】操作符详解3 + 指针 - 隐式类型转换 整型提升 算数转换 指针类型 野指针 -> 正文阅读

[C++知识库]【C语言】操作符详解3 + 指针 - 隐式类型转换 整型提升 算数转换 指针类型 野指针


表达式求值

表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。


一、隐式类型转换(整型提升 算数转换)

1、整型提升

C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升

1.2、整型提升的意义

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general - purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算。


1.3、如何进行整体提升呢?

整形提升是按照变量的数据类型的符号位来提升的 - 高位补充符号位
负数的整形提升, 高位补充符号位补1
正数的整形提升, 高位补充符号位补0
无符号整形提升,高位补0

#include <stdio.h>

int main()
{
	//b和c的值被提升为普通整型,然后再执行加法运算。
	//加法运算完成之后,结果将被截断,然后再存储于a中。
	
	char a = 3; //a是1字节 - 8比特位
	//00000000000000000000000000000011 - 3
	//00000011 - a
	char b = 127; //b是1byte - 8bit
	//00000000000000000000000001111111
	//01111111 - b

	//a和b都是char类型,大小都是1byte,所以这里计算的时候都要进行整型提升
	//高位补符号位
	//00000000000000000000000000000011 a整型提升补0
	//00000000000000000000000001111111 b
	//00000000000000000000000010000010 a+b

	char c = a + b;
	//10000010 截断
	//11111111111111111111111110000010 整型提升 负数补1
	//打印原码
	//11111111111111111111111110000001 反
	//10000000000000000000000001111110 原 2^7-2=128-2
	//-126
	printf("%d\n", c);

	return 0;
}

1.4、整形提升的例子

1.4.1、只有char和short会整型提升成int
int main()
{
	char a = 0xb6;
	short b = 0xb600;
	int c = 0xb6000000;

	if (a == 0xb6)
		printf("a");
	if (b == 0xb600)
		printf("b");
	if (c == 0xb6000000)
		printf("c");
	//11010110 - c
	//11111111111111111111111111010110 - 整型提升 负数补1
	//转为原码与0xb6不一样 第二个判断表达式也一样
	//c是int类型 不参与整型提升 判断条件一致 所以只打印c

	/*表达式a == 0xb6, b == 0xb600 的结果是假,
	但是c不发生整形提升, 则表达式 c==0xb6000000 的结果是真.*/
	return 0;
}
1.4.2、参与运算
int main()
{
	char c = 1;
	printf("%u\n", sizeof(c)); //1
	printf("%u\n", sizeof(+c)); //4
	//+-是操作符 表达式参与运算 char需要整型提升 变成整型
	printf("%u\n", sizeof(-c)); //4
	//c只要参与表达式运算, 就会发生整形提升, 表达式 + c, 就会发生提升, 所以 sizeof(+c) 是4个字节.
	//表达式 - c 也会发生整形提升, 所以 sizeof(-c) 是4个字节, 但是 sizeof(c), 就是4个字节
	return 0;
}

2、算数转换

long double
double
float
unsigned long int
long int
unsigned int
int

向上转换 排名较低的,首先要转换为另外一个操作数的类型后执行运算。

int main()
{
	int a = 4;
	float f = 4.5f;
	float r = a + f; // a算数转换成float

	return 0;
}

3、操作符的属性

3.1、复杂表达式的求值有三个影响的因素

  1. 操作符的优先级
  2. 操作符的结合性
  3. 是否控制求值顺序
3.1.1、优先级 结合性

两个相邻的操作符优先级不同的情况下,取决于优先级
两个相邻的操作符优先级相同时,取决于他们的结合性
// int c = a + b * 3 优先级 先算* 再算+
// int c = a + b + 3 优先级相同 看结合性 L-R 从左向右 先算a+b再算+3

3.1.2、是否控制求值顺序

// && 逻辑与(左边为假 右边就不用算了)
// || 逻辑或(左边为真,右边也不用算了)
// ?: 条件操作符(表达式1为真 2算3不算)
// , 逗号表达式(真正结果是最后一个表达式)

3.2、问题表达式

表达式1
//a* b + c * d + e * f

所以表达式的计算机顺序就可能是:
a* b
c* d
a* b + c * d
e * f
a * b + c * d + e * f
或者:
a* b
c* d
e* f
a* b + c * d
a * b + c * d + e * f

表达式2
//c + --c;
操作符的优先级只能决定自减–的运算在+的运算的前面,但是我们并没有办法得知, + 操作符的左操作数的获取在右操作数之前还是之后求值

代码3 非法表达式

int main()
{
 //在不同表达式结果都不同
	int i = 10;
	i = i-- - --i * (i = -3) * i++ + ++i;
	printf("i = %d\n", i);
	return 0;
}

代码4

int main()
{
	int a = 1;
	int d = 0;
	d = (++a) + (++a) + (++a);
	printf("%d\n", d);
	// vs2019是12 gcc是10
	//VS 三次加完4 4+4+4  GCC先算前两个是3 3+3+4

	//调试后 右击转到反汇编
	return 0;
}

在这里插入图片描述


代码5

int fun()
{
    static int count = 1;
    return ++count;
}
int main()
{
    int answer;
    answer = fun() - fun() * fun();
    printf("%d\n", answer);//输出多少?
    return 0;
}

静态局部变量 出函数范围不销毁 VS -10

总结:

我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。



指针

1、指针是什么

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向
(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以
说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址
的内存单元

  1. 地址指向了一个确定的内存空间,所以地址形象地被称为指针
  2. int* pa = &a; pa是用来存放地址(指针),所以pa是指针变量

总结:
指针是个变量,用来存放内存单眼的地址。(存放在指针中的值都被当成地址处理)。


1.1、一个单元1个字节

问题:
一个小的单元到底是多大?(1个字节)
如何编址?

对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的是产生一个电信号正电/负电(1或者0)
产生的二进制序列有2^32
这样的二进制序列,如果作为内存单元的编号,2^32次方个地址,能够管理2的32次方个内存单元
//bit
//byte
//kb
//mb
//gb
//tb
//pb…
2^32bit -> 4,294,967,296 ->/8/1034/1024/1024 ->0.5gb

在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,
所以一个指针变量的大小就应该是4个字节
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。


2、指针和指针类型

2.1、指针类型的意义

32位 指针变量大小都是4字节 为什么还要有这么多指针类型?

① 解引用

指针类型的意义1:
指针类型决定了指针解引用操作的时候,一次访问几个字节(访问内存大的大小)
char* 指针解引用访问1个字节
int* 指针解引用访问4个字节

int main()
{
	int a = 0x11223344; //8个二进制位是1字节

	//int* pa = &a;
	//*pa = 0; //改了4字节 (调试在内存中看)

	char* pc = &a;
	*pc = 0; //只改了1字节

	return 0;
}

② + -整数

指针类型的意义2:
指针类型决定了,指针+ -整数的时候的步长(指针±整数的时候,跳过了几个字节)
int* 指针+1 跳过了4个字节
char* 指针+1 跳过了1个字节

例:把每个整形里放1

int main()
{
	int arr[10] = { 0 }; //40字节
	int* p = arr; // arr数组名表示首元素地址 &arr[0] - int*
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = 1; // 0x00 00 00 01
		//+1访问1个整形
	}

	return 0;
}

每个字节里放1

int main()
{
	int arr[10] = { 0 };
	char* pa = arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(pa + i) = 1; //每次改1个字节
	}

	return 0;
}

总结:

指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。


3、野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

3.1、野指针成因

① 指针未初始化

int main()
{
	int* p; //没有初始化-里面放的是随机值

	*p = 20; //通过p中内存的随机数值作为地址,找到一个空间,
	//这个空间不属于我们当前的程序,就造成了非法访问,p就是野指针

	return 0;
}

② 指针越界访问

int main()
{
	int arr[10] = {0};
	int i = 0;
	int* p = arr;
	for (i = 0; i <= 10; i++) //0-10
	{
		//当指针指向的范围超出数组arr的范围时,p就是野指针
		*p = i;
		p++;
		// *(p++) = i
	}

	return 0;
}

③ 指针指向的空间释放

int* test() //返回类型int*
{
	int a = 10;
	return &a; //类型是int*
}

int main()
{
	int* p = test(); //指针用p接收

	printf("%d\n", *p); //指针变量p 存test的返回值 调用test 创建a变量 放10 返回到p
	//出作用域 还给操作系统 但此时p还是存了地址 非法访问

	return 0;
}

内存中的栈区是从高地址到低地址使用,高地址使用完再使用低地址


3.2、如何规避野指针

  1. 初始化指针
int main() {
	int a = 10;
	int* p = &a; //明确地初始化,确定指向

	int* p2 = NULL; //不知道一个指针当前应该指向哪里时,可以初始化为NULL
	//NULL本质上是0  强制转化成了((void*)0)
}
  1. 小心指针越界

  2. 指针指向空间释放即置成NULL

  3. 避免返回局部变量的地址(避免指针指向的空间被释放)

  4. 指针使用之前检查有效性

int main()
{
	int* p2 = NULL;
	*p2 = 100; //err
	//0指向的空间是不允许被使用的

	if (p2 != NULL)
	{
		*p2 = 100;
	}

	return 0;
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-01 14:19:22  更:2021-08-01 14:21:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/1 20:17:40-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码