C语言类型转换-自动类型转换、强制类型转换、指针类型转换

64 篇文章 98 订阅
本文详细介绍了C语言中的数据类型转换,包括自动类型转换和强制类型转换,以及转换规则和可能的风险。在混合运算和赋值过程中,编译器会自动进行类型提升以保证精度,但可能导致数据失真。强制类型转换允许程序员明确指定转换,但需要注意潜在的安全问题。文章还讨论了指针类型转换,强调了指针本质是变量,并举例说明了指针转换的用法和风险。此外,还探讨了结构体指针转换为数组指针以及函数参数使用void*的灵活性。
摘要由CSDN通过智能技术生成

数据类型转换就是将数据(变量、数值、表达式的结果等)从一种类型转换为另一种类型。

自动类型转换

自动类型转换就是编译器默默地、隐式地、偷偷地进行的数据类型转换,这种转换不需要程序员干预,会自动发生。

  1. 将一种类型的数据赋值给另外一种类型的变量时就会发生自动类型转换,例如:

float f = 100;

100 是 int 类型的数据,需要先转换为 float 类型才能赋值给变量 f。再如:

int n = f;

f 是 float 类型的数据,需要先转换为 int 类型才能赋值给变量 n。

在赋值运算中,赋值号两边的数据类型不同时,需要把右边表达式的类型转换为左边变量的类型,这可能会导致数据失真,或者精度降低;所以说,自动类型转换并不一定是安全的。对于不安全的类型转换,编译器一般会给出警告。

  1. 在不同类型的混合运算中,编译器也会自动地转换数据类型,将参与运算的所有数据先转换为同一种类型,然后再进行计算。转换的规则如下:
  • 转换按数据长度增加的方向进行,以保证数值不失真,或者精度不降低。例如,int 和 long 参与运算时,先把 int 类型的数据转成 long 类型后再进行运算。
  • 所有的浮点运算都是以双精度进行的,即使运算中只有 float 类型,也要先转换为 double 类型,才能进行运算。
  • char 和 short 参与运算时,必须先转换成 int 类型。

下图对这种转换规则进行了更加形象地描述:
img

unsigned 也即 unsigned int,此时可以省略 int,只写 unsigned。

自动类型转换示例:

#include<stdio.h>
int main(){
    float PI = 3.14159;
    int s1, r = 5;
    double s2;
    s1 = r * r * PI;
    s2 = r * r * PI;
    printf("s1=%d, s2=%f\n", s1, s2);
    return 0;
}

运行结果:
s1=78, s2=78.539749

在计算表达式r*r*PI时,r 和 PI 都被转换成 double 类型,表达式的结果也是 double 类型。但由于 s1 为整型,所以赋值运算的结果仍为整型,舍去了小数部分,导致数据失真。

强制类型转换

自动类型转换是编译器根据代码的上下文环境自行判断的结果,有时候并不是那么“智能”,不能满足所有的需求。如果需要,程序员也可以自己在代码中明确地提出要进行类型转换,这称为强制类型转换。

自动类型转换是编译器默默地、隐式地进行的一种类型转换,不需要在代码中体现出来;强制类型转换是程序员明确提出的、需要通过特定格式的代码来指明的一种类型转换。换句话说,自动类型转换不需要程序员干预,强制类型转换必须有程序员干预。

强制类型转换的格式为:

(type_name) expression

type_name为新类型名称,expression为表达式。例如:

(float) a;  //将变量 a 转换为 float 类型
(int)(x+y);  //把表达式 x+y 的结果转换为 int 整型
(float) 100;  //将数值 100(默认为int类型)转换为 float 类型

下面是一个需要强制类型转换的经典例子:

#include <stdio.h>
int main(){
    int sum = 103;  //总数
    int count = 7;  //数目
    double average;  //平均数
    average = (double) sum / count;
    printf("Average is %lf!\n", average);
    return 0;
}

运行结果:
Average is 14.714286!

sum 和 count 都是 int 类型,如果不进行干预,那么sum / count的运算结果也是 int 类型,小数部分将被丢弃;虽然是 average 是 double 类型,可以接收小数部分,但是心有余力不足,小数部分提前就被“阉割”了,它只能接收到整数部分,这就导致除法运算的结果严重失真。

既然 average 是 double 类型,为何不充分利用,尽量提高运算结果的精度呢?为了达到这个目标,我们只要将 sum 或者 count 其中之一转换为 double 类型即可。上面的代码中,我们将 sum 强制转换为 double 类型,这样sum / count的结果也将变成 double 类型,就可以保留小数部分了,average 接收到的值也会更加精确。

在这段代码中,有两点需要注意:

  • 对于除法运算,如果除数和被除数都是整数,那么运算结果也是整数,小数部分将被直接丢弃;如果除数和被除数其中有一个是小数,那么运算结果也是小数。这一点已在《C语言加减乘除运算》中进行了详细说明。
  • ( )的优先级高于/,对于表达式(double) sum / count,会先执行(double) sum,将 sum 转换为 double 类型,然后再进行除法运算,这样运算结果也是 double 类型,能够保留小数部分。注意不要写作(double) (sum / count),这样写运算结果将是 3.000000,仍然不能保留小数部分。

类型转换只是临时性的

无论是自动类型转换还是强制类型转换,都只是为了本次运算而进行的临时性转换,转换的结果也会保存到临时的内存空间,不会改变数据本来的类型或者值。请看下面的例子:

#include <stdio.h>
int main(){
    double total = 400.8;  //总价
    int count = 5;  //数目
    double unit;  //单价
    int total_int = (int)total;
    unit = total / count;
    printf("total=%lf, total_int=%d, unit=%lf\n", total, total_int, unit);
    return 0;
}

运行结果:
total=400.800000, total_int=400, unit=80.160000

注意看第 6 行代码,total 变量被转换成了 int 类型才赋值给 total_int 变量,而这种转换并未影响 total 变量本身的类型和值。如果 total 的值变了,那么 total 的输出结果将变为 400.000000;如果 total 的类型变了,那么 unit 的输出结果将变为 80.000000。

自动类型转换 VS 强制类型转换

在C语言中,有些类型既可以自动转换,也可以强制转换,例如 int 到 double,float 到 int 等;而有些类型只能强制转换,不能自动转换,例如以后将要学到的 void * 到 int *,int 到 char * 等。

可以自动转换的类型一定能够强制转换,但是,需要强制转换的类型不一定能够自动转换。现在我们学到的数据类型,既可以自动转换,又可以强制转换,以后我们还会学到一些只能强制转换而不能自动转换的类型。

可以自动进行的类型转换一般风险较低,不会对程序带来严重的后果,例如,int 到 double 没有什么缺点,float 到 int 顶多是数值失真。只能强制进行的类型转换一般风险较高,或者行为匪夷所思,例如,char * 到 int * 就是很奇怪的一种转换,这会导致取得的值也很奇怪,再如,int 到 char * 就是风险极高的一种转换,一般会导致程序崩溃。

使用强制类型转换时,程序员自己要意识到潜在的风险。

指针类型之间的转换

指向值的一个类型的指针可以转换为指向另一类型的指针。 但是,由于对齐需求和存储中不同类型的大小,结果可能是未定义的。 指向对象的指针可转换为指向其类型要求小于或等于严格存储对齐的对象的指针,然后再次返回而不做更改。

指向 void 的指针可转换为/自指向任何类型的指针,且不受限制或不丢失信息。 如果结果转换回原始类型,则将恢复原始指针。

如果指针转换为另一个类型相同但具有不同的或其它限定符的指针,则新指针与旧指针相同(新限定符强加的限制除外)。

指针值也可以转换为整数值。 根据以下规则,转换路径取决于指针的大小和整型的大小:

  • 如果指针的大小大于或等于整型的大小,则指针的行为类似于转换中的无符号值,除非它无法转换为浮点值。
  • 如果指针小于整型,则指针首先转换为与整型大小相同的指针,然后转换为整型。

相反,整型可以基于以下规则转换为指针类型:

  • 如果整型与指针类型的大小相同,则转换只会促使整数值被视为指针(无符号整数)。
  • 如果整型类型的大小与指针类型的大小不同,则使用表从带符号整型类型转换从无符号整型类型转换中给定的转换路径,首先将整型转换为指针的大小。 然后将其视为一个指针值。

值为 0 的整型常量表达式或强制转换为类型 void* 的此类表达式可以通过类型强制转换、赋值或与任何类型的指针进行比较来进行转换。 这将产生与同一类型的另一个 null 指针相等的 null 指针,但此 null 指针与指向函数或对象的任何指针不相等。 常数 0 以外的整数可以转换为指针类型,但结果是不可移植的。

指针的本质是变量,指针就是指针变量。
一个指针涉及两个变量:一个是指针变量自己本身,一个是指针变量指向的那个变量。
int *p; :定义指针变量时,p是int *类型,*p(p指向的那个变量)是int类型的。int *说白了就是指针类型,只要是指针类型都是占4个字节,解析方式都是按照地址方式来解释(意思是里面存的23个二进制加起来表示一个内存地址)的。

结论:所有指针类型(int *,double *,char *…)解析方式是相同的,都是地址。

对于指针所指向的那个变量来说,指针类型就很重要。指针指向的变量类型要取决于指针类型。

指针类型主要是为了其指向的变量所标记的。

#include <stdio.h>
int main(void)
{
        int a=5float *p;
        p=&a;
        printf("*p=%f.\n",*p);
}
#include <stdio.h>
int main(void)
{
        int a=5int *p1=&a;
        float *p;
        p=(float *)p1;    //强制类型转换
        printf("*p=%f.\n",*p);
}

指针的强制类型转换是有风险的

#include <stdio.h>
int main(void)
{
        int a=5char *p1=&a;
        printf("*p1=%d.\n",*p1);
        short *p2=&a;
        printf("*p2=%d.\n",*p2);

int和char类型都是整形,是兼容的,强制类型转换时有时候对有时候出错。int有两个字节char只有一个,int能表示的范围比int大,超过范围后int朝char转会出错。char往int就就不会出错(127)。short也有两个字节范围比char大还是比int小(65535)。

#include <stdio.h>
int main(void)
{
        int a[2]={0x11223344,ox55667788,0};
        int *p1=a;
        char *p2=(char *)a;
        printf("*p1=%x.\n",*p1);
        printf("*p2=%x.\n",*p2);
        printf("*p2=%x.\n",*(p2+1));
        printf("*p2=%x.\n",*(p2+2));
}

链接:https://www.jianshu.com/p/458928c7ec35

指针类型转换的应用

我们知道,所有指针都是指向一块内存的一个字节,然后根据指针的类型来对这个字节及其后面的字节进行解析。

因此我们可以利用这个特性来完成一些很方便的操作。

一、将结构体指针转换成数组指针,方便整体赋值操作

#include <string.h>
typedef struct 
{
    int a;
    int b;
    float c;
}ST;

int main(void) {
    ST st;
    memset((unsigned char*)&st, 0, sizeof(ST));
}

这样的好处是我们可以不用对结构体中的元素一个一个初始化,我们可以方便的操作结构体占用的内存。使用这种方法需要注意结构体中的内存对齐问题。

二、串口接收数据转换

问题背景可以参考这篇文章:https://mp.weixin.qq.com/s/cA4e9gvew5LYwZoVtdXZQw

三、函数、函数指针的参数和返回值

将函数参数设置成void *,可以让函数接收的数据类型更加灵活。

#include <stdio.h>
#define MSG_DRIVER_SIZE (10)
#define TRUE 1
#define FALSE 0

typedef struct msg_node{
	void *parm;
	void (*handler)(void *parm); 
}msg_node_t;  /* 消息数据结构 */

typedef struct msg_driver{
	unsigned int in;               //写入的位置
	unsigned int out;              //读出的位置
	msg_node_t *buf[MSG_DRIVER_SIZE];    
}msg_driver_t;

bool publish_msg(msg_driver_t *msg_buf, msg_node_t *msg)
{
	msg_buf->buf[msg_buf->in] = msg;
	msg_buf->in = (++msg_buf->in) % MSG_DRIVER_SIZE;    //防止越界
	
	return TRUE;
}

static msg_node_t *get_messge(msg_driver_t *msg_buf)
{
	msg_node_t *msg = NULL;
	
	msg = msg_buf->buf[msg_buf->out];
	msg_buf->out = (++msg_buf->out) % MSG_DRIVER_SIZE;    //防止越界
	
	return msg;
} 

void message_driver_handle(msg_driver_t *msg_buf)
{
	msg_node_t *msg;
	while( (msg = get_messge(msg_buf)) != NULL )
	{
		if (msg->handler != NULL)
			msg->handler(msg->parm);
	}
}

msg_driver_t msg_driver;

static void msg1_handle(void *parm)
{
	printf("gets msg1\r\n");
}

static void msg2_handle(void *parm)
{
	printf("get msg2\r\n");
}

static void msg3_handle(void *parm)
{
	printf("do msg3\r\n");
}

msg_node_t msg1 = {
	.parm = (void *)"I love u",
	.handler = msg1_handle
};

msg_node_t msg2 = {
	.parm = (void *)"I hate u",
	.handler = msg2_handle
};

msg_node_t msg3 = {
	.parm = NULL,
	.handler = msg3_handle
};

int main(void)
{
	publish_msg(&msg_driver, &msg1);
	publish_msg(&msg_driver, &msg2);
	publish_msg(&msg_driver, &msg3);
	
	while(1)
	{
		message_driver_handle(&msg_driver);
	}
}
  • 7
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
C语言中,强制类型转换指针是将一个指针变量转换为另一种指针类型的操作。这通常是在需要将一个类型的指针转换为另一个类型的指针时使用的。 一种常见的使用情况是将void指针转换为其他类型的指针,或者将其他类型的指针转换为void指针。在ANSI C中,void指针可以复制给其他任意类型的指针,其他任意类型的指针也可以复制给void指针,它们之间的复制不需要强制类型转换。 另一个常见的使用情况是在结构体之间进行强制转换。当两个结构体之间的成员相似或相同,但类型不同时,可以使用强制类型转换来将一个结构体转换为另一个结构体。 还有一种使用情况是在需要访问指针指向的内存中的特定字节时进行强制类型转换。例如,当一个指针指向一个整型数的起始位置时,可以使用强制类型转换指针转换为指向该整型数的第一个字节。 需要注意的是,在进行指针强制类型转换时要小心使用,确保转换后的指针在使用过程中不会导致未定义的行为或错误。 下面是一些相关的代码示例: 1、指针类型强制转换: ``` int m; int *pm = &m; char *cp = (char *)&m; ``` 2、结构体之间的强制转换: ``` struct str1 a; struct str2 b; a = *((struct str1*)&b); ``` 3、关于一个程序的解释: ``` int main(void) { int a = {1, 2, 3, 4}; int *ptr1 = (int *)(&a + 1); int *ptr2 = (int *)((int)a + 1); int *c = *(a + 1); printf("%x, %x, %x\n", ptr1[-1], *ptr2, *c); return 0; } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小熊coder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值