标签 博客 下的文章

C++咬文嚼字-'Functions'

关于Functions,Bjarne Stroustrup在'The C++ Programming Language'一书中是这么开篇的:'The typical way of getting something done in a C++ program is to call a function to do it.';另外他还阐述了一个使用Functions的原则:'A function cannot be called unless it has been previously declared.'。

函数的声明和定义要一致,这里的一致是指函数名字、返回类型和参数列表(不包括参数名字)的一致。参数名字是被编译器ignored的,所以在声明时你完全可以不显式写出参数名:
int func(char*, int); // a declaration with no explicit argument names
当然从代码的可读性等考虑,还是建议声明时写上参数名:
int func(char* array, int size);

声明和定义的参数名字可以不相同,编译器关注的是参数列表的各个参数类型。
int func(char* c, int i); //a declaration
int func(char *array, int size) {…} // a definition

这里补充一点就是如果你给某个函数声明了一个参数,你在实现这个函数时最好用上这个参数。

使用或者调用一个函数就涉及到另外两个方面了:参数传递(Argument passing)和函数返回值(Return value)。参数传递和函数返回值在语义上等价于变量初始化(initialization),与变量赋值(assignment)语义有区别。

这里首先区分一下变量初始化(initialization)与变量赋值(assignment):
很显然变量初始化发生在这个变量生命周期的最开始阶段,任何一个变量的生命都源于编译器给它分配一块存储区域,而初始化就紧跟在内存分配之后,用初始值覆盖这块内存,通过这两步操作完成一个对象(无论是built-in还是user-defined)的建构,换句简洁的话说:初始化是用来创建对象的,是创建对象的一个步骤;在未执行该步骤时,这个对象从语义上讲还不存在;而变量赋值操作则是修改一个对象,这个对象在被赋值之前已经存在了,有自己的存储空间并且已经有过初始化操作,是一个语义上真实存在的对象;赋值操作仅仅是改变了那块内存区域的值或者是bit序列分布而已。

上面说过参数传递(Argument passing)从语义上相当于变量初始化,那么当一个实参(Actual argument)被传递给一个函数后,究竟是哪个变量被初始化了或者说被创建了?初始化过程又是如何呢?举例说明:
int func(T value_formal_arg, T* ptr_formal_arg, T& ref_formal_arg); //这是一个函数声明,囊括了passed by value, passed by pointer, passed by reference全部三种参数传递方式

int main() {
 //…
 int rv = func(value_actual_arg, ptr_actual_arg, ref_actual_arg);
 //…
}

在参数传递过程中,完成了三个对象的创建,上面的func函数在传参的时候相当于:
int func(…) {
 T value_formal_arg = value_actual_arg;
 T* ptr_formal_arg = ptr_actual_arg;
 T& ref_formal_arg = ref_actual_arg;
 
 //接下来使用value_formal_arg,ptr_formal_arg和ref_formal_arg
}
从此可以看出,三个在function scope内的临时变量被在栈上分配内存,并分别被三个实参初始化了。当然有时候传进来的实参不能直接用于初始化,编译器要进行类型检查,看看传进来的实参是否匹配形参类型,对于兼容的类型做标准的或者用户自定义的implicit type conversions,对于完全不匹配的报告编译错误。
void print(int i) {…}

double d = 1.0;
print(d); //这里在传参的时候需要做一个将实参d转型为int型的implicit type conversion

void handle(int &i);

handle(1); //这里由于形参为non-const reference,而初始化语义不支持将常数直接用来初始化non-const reference,所以这里编译器会报告一个编译错误

说完了参数传递过程,接着看函数返回过程。前面同样提到了函数返回的语义也相当于变量初始化,那么我们同样要问这样一个问题:函数返回的过程中返回值到底用来创建哪个对象了?

T func(…) {
 //…
 return t; //t是T类型的
}

T rv;
rv = func(…);
当func返回时,返回值是用来初始化rv了么?根据上面我们所说的initialization和assignment的区别,我们可以断定这显然不是。那么返回值又是初始化哪个对象了呢?这里又有一个临时对象产生了。上面的语句等价于:
T rv;
T temp = t; //func的返回值t被用来创建一个临时对象temp
rv = temp; //这个临时对象最终被用来对rv进行assignment了

在C++中,以by value方式返回一个对象是效率不高的,特别是当返回复杂的user-defined类型时,其带来了额外的构造、拷贝构造和析构的损耗。在Scott Meyers的'More Effective C++'书中的条款20讲解了如何编写代码来配合编译器的'Return Value Optimization, RVO'优化技术,当编译器开启这种优化后,像上面我们的例子可以这么写:
T rv = func(…);

函数返回过程也许就会被优化成:
T rv = t;

C++的Function还提供了Default arguments机制,这里要注意的就是当函数有多个参数的时候,应该如何提供默认参数。比如:
void func(int a = 1, int b = 2, int c = 3); // ok
void func(int a, int b = 2, int c =3); // ok
void func(int a, int b, int c = 3); // ok
void func(int a = 1, int b, int c); // error

int main() {
 func(); //call func(1, 2, 3)
 func(5); //call func(5, 2, 3)
 func(5, 7); //call func(5, 7, 3)
}
知道void func(int a = 1, int b, int c);为什么错了吧?比如我调用func(5, 7);这实参5是传给a还是传给b呢?显然编译器不能resolve,那就只能给你报告错误了^_^。

C++咬文嚼字-'Evil cast'

Cast也被称为"Explicit Type Conversion",即显式类型转换,在传统C中强制转型(cast)只有一种语法形式(T)e。Bjarne Stroustrup在'The Design and Evolution of C++'(以后称作D&E)一书的14.3小节开始就说了'无论是从语法还是从语义上, Cast都是C++里最难看的特征之一',所以他要为cast提供A New Cast Notation.

旧式的cast有诸多问题,在D&E一书中有描述,这里摘录一些:
首先记法(T)e单一,语义上容易引起错误,使人们很难理解代码的真实意图,几乎每种类型组合都有某种合法的解释:
如:const X* px = new X(); 
    py = (Y*)px;   //这里很难明确是去掉const,还是取得基类访问权或者是强制转型为其他unrelated类型的指针

其次(T)e记法上过渡使用'()',这使得在代码里难辨别是否为cast,并且很难用grep这样的搜索工具快速检索定位。

Bjarne Stroustrup引入几种cast operators实际上是对传统cast的一个细致分类,而且提供的这几种cast operators的功能的总和和传统cast功能一致,这也为C++程序员完全不使用传统cast提供了充分的理由。Scott Meyers在其'More effective c++'的item2中就明确说'Prefer C++ style casts'。新的cast operators使用了长名字和类似于模版的使用语法,也便于工具检索。

C++提供四种新式cast operator:
static_cast<T>(e)
dynamic_cast<T>(e)
reinterpret_cast<T>(e)
const_cast<T>(e)

我将这些C++ style cast支持常见的cast都融入到了下面这个例子中,慢慢体会吧:

#include <iostream>

class Base {
        public:
                int     _x;
                virtual int func() {;}
};

class Derived : public Base {
        public:
                int     _y;
};

void print(char *str) {
        std::cout << str << std::endl;
}

class T;
class S;

void test_reinterpret_cast(T *pt) {
        S *ps = reinterpret_cast<S*>(pt);
}

int main() {
        void    *buf    = 0;

        //for static_cast
        double  d       = 2007.02;
        int     i       = 0;
        char    c       = 'A';
        i = static_cast<int>(d);        //ok, static_cast支持non-pointer类型的conversion
        std::cout << i << std::endl;

        i = static_cast<int>(c);        //ok, static_cast支持non-pointer类型的conversion
 std::cout << i << std::endl;

        int     *p      = 0;
        buf = operator new(100);
        p = static_cast<int*>(buf);     //ok, static_cast支持从void*到任意指针类型
        (*p) = 2008;
        std::cout << *p << std::endl;

        Base    *pb     = new Derived();
        Derived *pd     = static_cast<Derived*>(pb); //ok, static_cast利用静态类型信息完成类层次间的转型
        pd->_y          = 2009;
        pd->Base::_x    = 2010;

        std::cout << pb->_x << std::endl;
        std::cout << pd->_x << std::endl;
        std::cout << pd->_y << std::endl;

        pb      = new Base();
        pd      = static_cast<Derived*>(pb); //ok, but pd is trivial and it is very dangerous to use it.
        //pd->_y = 2011; //dangerous, may cause crash

        //for const_cast
        const int       k = 2012;
        //int           m = const_cast<int>(k); //error: const_cast不支持non-pointer类型的conversion

        const char* str = "hello";
        //print(str);   //error: invalid conversion from `const char*' to `char*'
        print(const_cast<char*>(str));

        //for dynamic_cast ,
        //Base class should be a polymorphic class, otherwise the dynamic_cast operator will prevent you from doing cast

        pb      = new Derived();
        pd     = dynamic_cast<Derived*>(pb);
        if (pd) {
  std::cout <<"downcast ok\n"; //we will see this
        } else {
                std::cout <<"downcast error\n";
        }

        pb      = new Base();
        pd     = dynamic_cast<Derived*>(pb);
        if (pd) {
                std::cout <<"downcast ok\n";
        } else {
                std::cout <<"downcast error\n"; // we will see this. dynamic_cast会在运行时利用运行时类型信息判断动态类型到底
是不是一个真实的Derived Object.
        }

        //for reinterpret_cast
        char    ch      = 'A';
        char    *pch    = &ch;
        //int   h       = reinterpret_cast<int>(ch); // reinterpret_cast不支持这种non-pointer conversion
 &nb
sp;      int   h       = reinterpret_cast<int>(pch); //ok, reinterpret_cast支持这种将指针值转换成整数的conversion
        std::cout << h << std::endl;
        int     *ph     = reinterpret_cast<int*>(h); //ok, reinterpret_cast支持这种将任意整型数转换成地址的操作
        //std::cout << *ph << std::endl; //may cause crash

        //see test_reinterpret_cast function的定义, reinterpret_cast can
        //converts any pointer type to any other pointer type, even of unrelated classes.
        //and reinterpret_cast不需要知道类型的定义, 正如test_reinterpret_cast涉及到的
        //M和T,在这里仅仅是两个forward declaration

}

Cast毕竟还是cast,依旧是很多Evil的源头,尽管C++做了细化和改善还是尽量少用的好,尽量减少cast的操作。总体来说Bjarne Stroustrup以及C++委员会的工作还是很有意义的。减轻罪恶(evil)就是造福,不是吗?^_^

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats