全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-708-3566

c/c++ 奇技淫巧(一些c语言的技巧)

一. 变长数组

严格说来,变长数组的实现在c++中并不是一件麻烦的事情。Stl中的vector本身就是一个变长数组,并且有自动管理内存的能力。
但是在c中,实现变长数组就稍显麻烦。用C实现,必然需要一个结构,结构当中应当有一个指针,指针分配一段内存空间,空间大小根据需要而定,而且必须有另外一个字段记录究竟开辟了多大多长的空间。
大致描述如下:

Struct MutableLenArray
{
  Int count;
  Char* p;
};

P = new Char[Count];

没什么问题,但是C语言的使用者有个最大的自豪就在于对于效率、空间使用的掌控。他们会有这样的疑问,如果count=0,那么p就没必要了,白白占了4(64位系统为8)个字节的空间,简直浪费。
那有没有更好的方式能实现上面的需求,又保证空间合理呢?答案是有的,用0长度

Struct MutableLenArray 
{ 
Int count; 
Char p[0]; 
}; 

和上面的结构使用方法一致,但是我们可以用sizeof尝试读取其大小,发现竟然只有count字段的长度4字节,p没有被分配空间。完美!

二. 宏的妙用

1. #和

“#”符号把一个符号直接转换为字符串,例如:

#define TO_STRING(x) #x 
const char *str = TO_STRING( test ); 

str的内容就是”test “,也就是说#会把其后的符号 直接加上双引号。
这个特性为c++反射的实现提供了极大便利,可以参考博主的下一篇文章,c++反射的简单实现。

##符号会连接两个符号,从而产生新的符号(词法层次),例如:

#define SIGN( x ) INT_##x 
  int SIGN( 1 ); 

宏被展开后将成为:int INT_1;
可以把##看成连字符,连字符为则为新符号的产生提供了方便。Google的Gtest框架就巧妙的运用了连字符来生成新的测试案例。

2. 变参宏

#define LOG( format, ... ) printf( format, __VA_ARGS__ ) 
  LOG( "%s %d", str, count ); 

VA_ARGS是系统预定义宏,被自动替换为参数列表。
经常需要进行输出格式化,重定义时,可以用到以上技巧。

3. 宏参数的prescan

prescan的定义:当一个宏参数被放进宏体时,这个宏参数会首先被全部展开(有例外,见下文)。当展开后的宏参数被放进宏体时, 预处理器对新展开的宏体进行第二次扫描,并继续展开。例如:

#define PARAM( x ) x 
  #define ADDPARAM( x ) INT_##x 
  PARAM( ADDPARAM( 1 ) ); 

因为ADDPARAM( 1 ) 是作为PARAM的宏参数,所以先将ADDPARAM( 1 )展开为INT_1,然后再将INT_1放进PARAM。
例外情况是,如果PARAM宏里对宏参数使用了#或##,那么宏参数不会被展开:

#define PARAM( x ) #x 
  #define ADDPARAM( x ) INT_##x 

PARAM( ADDPARAM( 1 ) ); 将被展开为”ADDPARAM( 1 )”。

所以此时要得到“INT_1”的结果,必须加入一个中间宏:

#define PARAM(x) PARAM1(x)
#define PARAM1( x ) #x 

PARAM( ADDPARAM( 1 ) );此时的结果将会是“INT_1”。根据prescan原则,当ADDPARAM(1)传入,会展开得到INT_1,然后将INT_1带入PARAM1宏,最终得到“INT_1”的结果。

4. 接口宏

以下部分,摘自网上博客,仅作声明。
C++的目标之一就是把类的声明和定义分离开来,这对于项目的开发极其有利——这可以使开发人员不用看到类的实现就能知晓类的功能。但是,C++实现类的声明与类定义的分离的方法会导致一些额外的工作——每个非内联函数的表示都需要写两次,一次在类声明中,一次在类定义中。
代码如下:

// .h File 
class Element 
{ 
void Tick (); 
};

// .cpp File 
void Element ::Tick () 
{ 
// todo 
}

由于Tick的标识在两个地方都出现了,因此如果我们需要改变这个方法的参数的时候(改变函数名、返回类型或者加const),我们需要改变两个地方。
当然通常这没有什么工作量,但是有些情况下这个特性会带来不少麻烦。
举个例子,如果我们有一个叫做BaseClass的基类,有三个从BaseClass继承而来的子类——D1、D2和D3.其中BaseClass声明了一个虚函数Foo()并且有一个缺省实现,并且D1、D2、D3中重载了Foo()函数。
现在,如果说我们给BaseClass::Foo()添加一个参数,但是忘了给D3中做相应的修改。麻烦来了——编译可以通过,编译器会把BaseClass::Foo(…)和D3::Foo()当成两个完全不同的函数。当我们想通过虚函数机制来调用D3的Foo的时候,这就容易出一些问题。
UE4中光继承自AActor类的类就有上千个,如果需要对AActor类做一个修改,那么如果使用传统方法,我们还要针对上千个派生类进行修改,而且万一有一个派生类没有修改,编译器也不会报错!
这么看来,理想的情况是我们希望一个函数的表示只在一个地方存在,如果说只声明BaseClass::Foo()一次,然后再它的派生类中不用再额外声明Foo就好了。
而且在效率方面来说,在C++中使用继承的时候我们经常会使用很多浅层次的类继承关系,一个父类往往有一堆子类。很多时候我们只需要把很多互不相关的功能集成到一个单独的类继承家族里面。
对于浅继承来说,我们只是把开始的父类声明为一个接口——也就是说它声明了一些虚函数(大部分是纯虚函数)。在大多数情况下,我们会在这个类家族里面有一个基类以及其余的派生类。
如果说我们的基类有10个函数,我们从这个基类派生了20个类,那么我们就需要额外做200个函数声明。但是这些声明的目的往往只是为了Implement基类中的那些方法而已,这就或多或少的容易使得头文件不好维护。
传统方法的实现
如果说我们有一个Animal的类,这个类被视为基类,我们希望从这个基类派生出不同的子类。在Animal中有3个纯需函数,如下所示:

class Animal 
{ 
public: 
virtual std :: string GetName () const = 0 ; 
virtual Vector3f GetPosition () const = 0; 
virtual Vector3f GetVelocity () const = 0; 
}; 

同时,这个基类拥有三个派生类——Monkey,Tiger,Lion。
那么我们三个方法的每一个都会在7个地方存在:Animal中一次,Monkey、Lion、Tiget的声明和定义各一次。
然后假设我们做一个小改动——我们想将GetPosition和GetVelocity的返回类型改为Vector4f以适应Transform变换,那么我们就要在7个地方进行修改:Animal的.h文件,Lion、Tiger和Monkey的.h文件和.cpp文件。
使用宏的实现
有一种很妙的处理方法就是将这些方法进行包装,改成所谓接口宏的形式。我们可以试试看:

#define INTERFACE_ANIMAL(terminal)             \
public:                           \
  virtual std::string GetName() const ##terminal     \
  virtual IntVector GetPosition() const ##terminal    \
  virtual IntVector GetVelocity() const ##terminal    

#define BASE_ANIMAL   INTERFACE_ANIMAL(=0;)
#define DERIVED_ANIMAL INTERFACE_ANIMAL(;)

值得一提的是,##符号代表的是连接,\符号代表的是把下一行的连起来。
通过这些宏,我们就可以大大简化Animal的声明,还有所有从它派生的类的声明了:

// Animal.h
class Animal
{
  BASE_ANIMAL ;
};



// Monkey.h
class Monkey : public Animal
{
  DERIVED_ANIMAL ;
};


// Lion.h
class Lion : public Animal
{
  DERIVED_ANIMAL ;
};


// Tiger.h
class Tiger : public Animal
{
  DERIVED_ANIMAL ;
};


# c++  # 奇技淫巧  # C语言算法积累分离数位示例  # c语言分离三位数的实现  # 有一个  # 的是  # 如果说  # 子类  # 变长  # 派生类  # 会在  # 这就  # 做一个  # 会把  # 类中  # 有什么  # 也不  # 来了  # 会有  # 有个  # 就能  # 将会  # 就有  # 有一种 


相关文章: 魔方云NAT建站如何实现端口转发?  婚礼视频制作网站,学习*后期制作的网站有哪些?  微信推文制作网站有哪些,怎么做微信推文,急?  浙江网站制作公司有哪些,浙江栢塑信息技术有限公司定制网站做的怎么样?  动图在线制作网站有哪些,滑动动图图集怎么做?  jQuery 常见小例汇总  PHP正则匹配日期和时间(时间戳转换)的实例代码  ,想在网上投简历,哪几个网站比较好?  微信小程序制作网站有哪些,微信小程序需要做网站吗?  学校建站服务器如何选型才能满足性能需求?  如何在服务器上配置二级域名建站?  如何在局域网内绑定自建网站域名?  ppt制作免费网站有哪些,ppt模板免费下载网站?  Python lxml的etree和ElementTree有什么区别  宝塔新建站点为何无法访问?如何排查?  建站主机与虚拟主机有何区别?如何选择最优方案?  建站之星伪静态规则如何正确配置?  上海制作企业网站有哪些,上海有哪些网站可以让企业免费发布招聘信息?  如何高效配置香港服务器实现快速建站?  厦门模型网站设计制作公司,厦门航空飞机模型掉色怎么办?  建站主机默认首页配置指南:核心功能与访问路径优化  如何选择长沙网站建站模板?H5响应式与品牌定制哪个更优?  如何快速生成ASP一键建站模板并优化安全性?  建站VPS推荐:2025年高性能服务器配置指南  如何用腾讯建站主机快速创建免费网站?  如何在万网自助建站中设置域名及备案?  定制建站平台哪家好?企业官网搭建与快速建站方案推荐  c++23 std::expected怎么用 c++优雅处理函数错误返回【详解】  网站企业制作流程,用什么语言做企业网站比较好?  建站之星2.7模板快速切换与批量管理功能操作指南  如何快速搭建FTP站点实现文件共享?  如何在万网主机上快速搭建网站?  如何在橙子建站中快速调整背景颜色?  上海网站制作网页,上海本地的生活网站有哪些?最好包括生活的各个方面的?  建站之星如何修改网站生成路径?  阿里云高弹*务器配置方案|支持分布式架构与多节点部署  如何选择域名并搭建高效网站?  如何选择美橙互联多站合一建站方案?  如何解决VPS建站LNMP环境配置常见问题?  建设网站制作价格,怎样建立自己的公司网站?  正规网站制作公司有哪些,目前国内哪家网页网站制作设计公司比较专业靠谱?口碑好?  c# 在ASP.NET Core中管理和取消后台任务  公司网站建设制作费用,想建设一个属于自己的企业网站,该如何去做?  制作网站软件推荐手机版,如何制作属于自己的手机网站app应用?  英语简历制作免费网站推荐,如何将简历翻译成英文?  linux top下的 minerd 木马清除方法  如何选择香港主机高效搭建外贸独立站?  建站主机服务器选型指南与性能优化方案解析  制作网站的过程怎么写,用凡科建站如何制作自己的网站?  网站制作软件有哪些,制图软件有哪些? 

您的项目需求

*请认真填写需求信息,我们会在24小时内与您取得联系。