用C语言的泛型实现交换两个变量值

第一种,最常用的是创建一个中间变量来循环交换它们的值:

T a = ...;
T b = ...;
.
T tmp = a; a = b; a = tmp;

我们称这种策略p99_swap1。在这里,编译器必须严格实现三个任务的顺序,否则,由此程序产生的结果将是不正确的。

第二种,叫它p99_swap2,试图做类似的事情,但放松一些顺序约束:

T a = ...;
T b = ...;
.
T tmpa = a; T tmpb = b;
a = tmpb; b = tmpa;

用更多的资源(栈空间或寄存器)可以产生更有效的代码。两个对象可以平行地加载和保存。但收益可能只在小对象上可以看到。所以将两者结合起来是一个可能的尝试

#define P99_SWAP(A, B) (sizeof(A) > sizeof(uintmax_t) ? P99_SWAP1(A, B) : P99_SWAP2(A, B))

但是如何实现两个 “子宏” P99_SWAP1 P99_SWAP2(A, B) ?如果我们想使用C的宏或者函数来实现的难度在于仅仅是传递参数A和B而不知道其类型,所以让我们先写函数和宏,忘记类型问题:

inline
void p00_swap2(void* a, void* b, void* tmpa, void* tmpb, size_t len) {
  memcpy(tmpa, a, len);
  memcpy(tmpb, b, len);
  memcpy(b, tmpa, len);
  memcpy(a, tmpb, len);
}
#define P00X_SWAP2(A, B) p00_swap2(
  &(A),                   \
  &(B),                   \
  (char[sizeof(A)]){ [0] = 0 },       \
  (char[sizeof(A)]){ [0] = 0 }, sizeof(A))

这个古怪的表达式: (char[sizeof(A)]){ [0] = 0 } 被称为复合文字(C99新特性),为复制操作提供临时对象。

这有几个缺点。首先,我们甚至没有检查是否A和B与对象具有相同的大小,但我们很愉快地复制到他们。因此,首先,我们必须断言它们至少具有相同的大小,避免引起不确定的行为。这样就可以为两个复合文字实现一些表达上的魔法:

(char[sizeof(A)]){ [(intmax_t)sizeof(A) - sizeof(B)] = 0 }

其中:intmax_t类型指定一个最大尺寸有符号整数

这里发生了什么?右边的[]里面,一个指定的初始值,被用来初始化字符数组中的一个元素。现在我们将比较两者的大小:如果两者相等,则表示位置0处的元素,如果sizeof(A) < sizeof(B) ,类型转换 intmax_t 在编译的期间将产生一个负数。

如果现在我们将上面的策略应用于第二个复合文字,我们得到一个宏,在它调用两个相同大小的对象的时候成功编译,并在大小不同的时候在编译期间产生错误:

#define P00_SWAP2(A, B)
p00_swap2(                           \
   &(A),                            \
   &(B),                            \
   (char[sizeof(A)]){ [(intmax_t)sizeof(A) - sizeof(B)] = 0 }, \
   (char[sizeof(B)]){ [(intmax_t)sizeof(B) - sizeof(A)] = 0 }, \
   sizeof(A))

这现在已经是更安全,但也许还不够安全,因为这两个对象可能有相同的大小,但仍然不是同一类型。我们可以做一个额外的检查来确定这两种类型是否是兼容的。这可以通过下面这样的可能第一眼看起来有点hack

(1 ? &(A) : ((A = B), NULL))
#define P99_SWAP(A, B) (sizeof(A) > sizeof(uintmax_t) ? P99_SWAP1(A, B) : P99_SWAP2(A, B))

这里的条件总是真,所以它总是等于&(A)。第二个“假”部分在运行时从未执行,但只用来检查它是否是正确的C代码。如果A和B不会兼容,则表示不是正确的C代码。

以上就是这篇文章的全部内容,希望本文的内容对大家的学习和工作能带来帮助,如果有疑问可以留言交流。

(0)

相关推荐

  • c语言全局变量和局部变量问题及解决汇总

    1.局部变量能否和全局变量重名? 答:能,局部会屏蔽全局.要用全局变量,需要使用"::" 局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量.对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内. 2.如何引用一个已经定义过的全局变量? 答:extern 可以用引用头文件的方式,也可以用extern关键字,如果用引用头文件方式来引用某个在头文件中声明的全

  • C语言 指针变量作为函数参数详解

    在C语言中,函数的参数不仅可以是整数.小数.字符等具体的数据,还可以是指向它们的指针.用指针变量作函数参数可以将函数外部的地址传递到函数内部,使得在函数内部可以操作函数外部的数据,并且这些数据不会随着函数的结束而被销毁. 像数组.字符串.动态分配的内存等都是一系列数据的集合,没有办法通过一个参数全部传入函数内部,只能传递它们的指针,在函数内部通过指针来影响这些数据集合. 有的时候,对于整数.小数.字符等基本类型数据的操作也必须要借助指针,一个典型的例子就是交换两个变量的值. 有些初学者可能会使用

  • C语言中通过LUA API访问LUA脚本变量的简单例子

    1.简介 这一节介绍一些关于栈操作.数据类型判断的LUA API,可以使用这些函数获得脚本中的变量值. 2.步骤 编写 test01.lua 脚本,在VS2003中创建控制台C++程序并正确配置,执行查看结果,修改test02.lua脚本后查看执行结果 3.测试脚本 以下是用来测试的lua脚本 复制代码 代码如下: function plustwo(x)          local a = 2;          return x+a; end; rows = 6; cols = plustw

  • C语言中判断int,long型等变量是否赋值的方法详解

    当然,如果你不赋值给局部变量,这样会导致整个程序的崩溃,因为,它的内容被系统指向了垃圾内存.下面我们看一段代码: 复制代码 代码如下: #include <stdio.h>#include <string.h>#include <stdlib.h>int globle_value;int my_sum(int value1, int value2);long my_sub(long value1, long value2);int main(void){ int aut

  • C语言中变量与其内存地址对应的入门知识简单讲解

    先来理解理解内存空间吧.请看下图: 如上图所示,内存只不过是一个存放数据的空间,就好像我的看电影时的电影院中的座位一样.电影院中的每个座位都要编号,而我们的内存要存放各种各样的数据,当然我们要知道我们的这些数据存放在什么位置吧.所以内存也要象座位一样进行编号了,这就是我们所说的内存编址.座位可以是遵循"一个座位对应一个号码"的原则,从"第1号"开始编号.而内存则是按一个字节接着一个字节的次序进行编址,如上图所示.每个字节都有个编号,我们称之为内存地址.好了,我说了这

  • C语言变量类型与输出控制用法实例教程

    本文实例讲述了C语言变量类型与输出控制用法,有助于读者很好的对其进行总结与归纳.该实例分享给大家供大家参考借鉴之用.具体如下: 完整实例代码如下: /********************************************** **<Beginning C 4th Edition>Notes codes ** Created by Goopand ** Compiler: gcc 4.7.0 *******************************************

  • C语言 全局变量和局部变量详解及实例

    C语言 全局变量和局部变量详解 核心内容: 1.局部变量和全局变量 变量按照作用域分为:全局变量和局部变量 全局变量的作用域:从定义位置开始到下面整个程序结束. 局部变量的作用域:在一个函数内部定义的变量只能在本函数内部进行使用. OK,上面的效果用Java语言实现一下: public class App1 { public static int k = 10;//相当于全局变量 public static void main(String[] args) { int i = 10;//局部变量

  • 详解C语言的结构体中成员变量偏移问题

    c语言中关于结构体的位置偏移原则简单,但经常忘记,做点笔记以是个记忆的好办法 原则有三个: a.结构体中的所有成员其首地址偏移量必须为器数据类型长度的整数被,其中第一个成员的首地址偏移量为0, 例如,若第二个成员类型为int,则其首地址偏移量必须为4的倍数,否则就要"首部填充":以此类推 b.结构体所占的总字节数即sizeof()函数返回的值必须是最大成员的长度的整数倍,否则要进行"末尾填充": c.若结构体A将结构体B作为其成员,则结构体B存储的首地址的偏移量必须

  • 深入探讨C语言中局部变量与全局变量在内存中的存放位置

    C语言中局部变量和全局变量变量的存储类别(static,extern,auto,register) 1.局部变量和全局变量在讨论函数的形参变量时曾经提到,形参变量只在被调用期间才分配内存单元,调用结束立即释放.这一点表明形参变量只有在函数内才是有效的,离开该函数就不能再使用了.这种变量有效性的范围称变量的作用域.不仅对于形参变量,C语言中所有的量都有自己的作用域.变量说明的方式不同,其作用域也不同.C语言中的变量,按作用域范围可分为两种,即局部变量和全局变量.1.1局部变量局部变量也称为内部变量

  • C语言中交换int型变量的值及转换为字符数组的方法

    不使用其他变量交换两个整型的值: #include <stdio.h> void main(){ int a = 3; int b = 4; a = a ^ b;//使用异或交换 b = b ^ a; a = a ^ b; printf("%d, %d\n", a, b); a = a - b;//使用加减交换 b = a + b; a = b - a; printf("%d, %d\n", a, b); a ^= b ^= a ^= b; printf

  • 在C语言编程中使用变量的基础教程

    C语言在明面上将数的变量分为两类,整型变量以及浮点数,对应着现实世界的整数和小数. 首先是整数,使用了这么多的C语言之后,每当在使用整数之时都会将其想象成二进制的存在,而不是十进制.原因在于,这是程序的本质所在,稍有研究编译器工作原理的都会发现,在编译器处理乘法乃至除法的时候,优秀的编译器总会想方设法的加快程序的速度,毫无疑问在所有运算中移位运算是最快速的"乘法"以及"除法": 1<<2 == 4 ,8>>2 == 2 而正常一个乘法相当于十

  • C语言基础知识变量的作用域和存储方式详细介绍

    变量的作用域和存储方式 1.简述变量按作用域的分类 变量按作用域分:分为全局变量和局部变量 全局变量:在所有函数外部定义的变量叫做全局变量 全局变量的使用范围:从定义位置开始到下面整个程序结束 局部变量:在一个函数内部定义的变量或者函数的形式参数统称为局部变量 局部变量的使用范围:在函数内部定义的变量只能在本函数内部进行使用 2.简述变量按存储方式的分类 静态变量 自动变量 寄存器变量[寄存器就是cpu内部可以存储数据的一些硬件东西] 3.简述全局变量和局部变量命名冲突的问题 1>在一个函数内部

  • C语言 常量,变量及数据详细介绍

    一.数据 图片文字等都是数据,在计算机中以0和1存储. (一)分类 数据分为静态数据和动态数据. ①. 静态数据:一些永久性的的数据,一般存储在硬盘中,只要硬盘没坏数据都是存在的.一般以文件的形式存储在硬盘上,电脑关机重启后依然存在. ②. 动态数据:程序运行过程中,动态产生的的临时数据,一般存储在内存中,内存的存储空间一般较小,计算机关闭后这些数据就会被清除.软件或者电脑关闭则这些临时数据会被清除. ③. 静态数据和动态数据可以转换. ④. 注意:为什么不把动态数据存放到硬盘?因为直接访问内存

  • C语言中结构体偏移及结构体成员变量访问方式的问题讨论

    c语言结构体偏移 示例1 我们先来定义一下需求: 已知结构体类型定义如下: struct node_t{ char a; int b; int c; }; 且结构体1Byte对齐 #pragma pack(1) 求: 结构体struct node_t中成员变量c的偏移. 注:这里的偏移量指的是相对于结构体起始位置的偏移量. 看到这个问题的时候,我相信不同的人脑中浮现的解决方法可能会有所差异,下面我们分析以下几种可能的解法: 方法1 如果你对c语言的库函数比较熟悉的话,那么你第一个想到的肯定是of

随机推荐