• linkedu视频
  • 平面设计
  • 电脑入门
  • 操作系统
  • 办公应用
  • 电脑硬件
  • 动画设计
  • 3D设计
  • 网页设计
  • CAD设计
  • 影音处理
  • 数据库
  • 程序设计
  • 认证考试
  • 信息管理
  • 信息安全
菜单
linkedu.com
  • 网页制作
  • 数据库
  • 程序设计
  • 操作系统
  • CMS教程
  • 游戏攻略
  • 脚本语言
  • 平面设计
  • 软件教程
  • 网络安全
  • 电脑知识
  • 服务器
  • 视频教程
  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号
您的位置:首页 > 程序设计 >C语言 > c/c++ 奇技淫巧(一些c语言的技巧)

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

作者: 字体:[增加 减小] 来源:互联网 时间:2017-05-28

通过本文主要向大家介绍了c语言和c++有什么区别,c语言与c++的区别,c语言和c++,c语言和c++先学哪个,c语言跟c++有什么区别等相关知识,希望对您有所帮助,也希望大家支持linkedu.com www.linkedu.com

一. 变长数组

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

Struct MutableLenArray
{
  Int count;
  Char* p;
};
</div>

P = new Char[Count];

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

Struct MutableLenArray 
{ 
Int count; 
Char p[0]; 
}; 
</div>

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

二. 宏的妙用

1. #和

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

#define TO_STRING(x) #x 
const char *str = TO_STRING( test ); 
</div>

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

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

#define SIGN( x ) INT_##x 
  int SIGN( 1 ); 
</div>

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

2. 变参宏

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

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

3. 宏参数的prescan

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

#define PARAM( x ) x 
  #define ADDPARAM( x ) INT_##x 
  PARAM( ADDPARAM( 1 ) ); 
</div>

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

#define PARAM( x ) #x 
  #define ADDPARAM( x ) INT_##x 
</div>

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

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

#define PARAM(x) PARAM1(x)
#define PARAM1( x ) #x 
</div>

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 
}
</div>

由于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; 
}; 
</div>

同时,这个基类拥有三个派生类——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(;)
</div>

值得一提的是,##符号代表的是连接,\符号代表的是把下一行的连起来。
通过这些宏,我们就可以大大简化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 ;
};
</div>

</div>
分享到:QQ空间新浪微博腾讯微博微信百度贴吧QQ好友复制网址打印

您可能想查找下面的文章:

  • C++语言数据结构 串的基本操作实例代码
  • C/C++ 中堆和栈及静态数据区详解
  • c/c++ 奇技淫巧(一些c语言的技巧)
  • C/C++中运算符的优先级、运算符的结合性详解
  • C/C++ 中sizeof('a')对比详细介绍
  • C/C++中的typedef和#define详解
  • C/C++中宏定义(#define)
  • C/C++函数参数传递机制详解及实例
  • C/C++ 公有继承、保护继承和私有继承的对比详解
  • C/C++静态类和this指针详解及实例代码

相关文章

  • 2017-05-28深入解析C++中的指针数组与指向指针的指针
  • 2017-05-28C++智能指针实例详解
  • 2017-05-28C++基础入门教程(七):一些比较特别的基础语法总结
  • 2017-05-28浅谈int8_t int64_t size_t ssize_t的相关问题(详解)
  • 2017-05-28C++中virtual继承的深入理解
  • 2017-05-28基于WTL中使用双缓冲避免闪烁的解决方法
  • 2017-05-28wchar_t,char,string,wstring之间的相互转换
  • 2017-05-28C语言 数据结构之链表实现代码
  • 2017-05-28数据结构之Treap详解
  • 2017-05-28C语言 共用体(Union)详解及示例代码

文章分类

  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号

最近更新的内容

    • c++基础语法:构造函数与析构函数
    • C 语言指针变量详细介绍
    • C++基于对话框的程序的框架实例
    • c语言全盘搜索指定文件的实例代码
    • C语言之单链表的插入、删除与查找
    • vc中使用SendMessage自定义消息函数
    • C++之Boost::array用法简介
    • C++ Qt属性系统详细介绍
    • C语言 函数指针(指向函数的指针)详解
    • 深度理解c++中的this指针

关于我们 - 联系我们 - 免责声明 - 网站地图

©2020-2025 All Rights Reserved. linkedu.com 版权所有