C++进阶之 函数

入口函数main:

main()函数是C++的入口函数,一般情况返回值为int类型(这也是C++标准的要求,所以部分严格的编译器要求main返回值必须为int),和正常函数不同的是,如果main函数没有显式出现return,大部分编译器会默认添加return 0而不是编译失败。

虽然main函数是入口函数,但并不代表一定是第一个执行的函数。所有外部对象的构造函数、类中的静态对象有初始化时这些对象的构造函数都会先于main函数执行。

main函数可以带参数

main(int argc, char* argv[]){...}

函数参数的传递:

__cdecl 是c和c++程序默认的函数调用约定,参数从右到左的顺序入栈,由主调函数维护堆栈(调用前压入,调用结束后弹出)

传递的参数不是简单的变量而是表达式时,需要先将表达式化简,然后在实现函数调用。

(详细过程和c没区别,不写了。。。)

函数参数的默认值:

C++语言允许指定函数参数的默认值。

1.最好在函数声明时指明参数默认值,而在函数定义时加入注释。否则分离编译时可能出现问题。

2.不带默认值的参数要在带默认值参数的左边。(防止二义性)

3.参数默认值可能会带来函数重载的二义性。

 

函数调用:

传值调用和引用调用,基础类型时两种区别不大,但是在参数为类类型来说,区别很大,因为传值调用会在进入函数体之前在堆栈上建立一个实参的副本,而引用调用则没有这个操作,所以性能更好。

禁止传值调用:

某些场景下可能考虑禁止传值调用,由于建立副本的操作时利用拷贝构造函数进行的,所以,要禁止传值调用,只要把拷贝构造函数显式定义并设定为private或者protect即可。

class A
{
private:
    A(const A&){};
}

void show(A a) {}

void test()
{
    A a;
    show(a); //编译不通过
}

不过这样也有问题,就是也阻止了类对象做为函数的返回值,所以,如果有需要,这种情况下需要返回对象的引用。

可变长的参数:

(有专门说过,不写了。。。)

函数指针:

ReturnType  (*pfunc) (var_list) 

ReturnType为返回类型 pfunc为函数指针名 var_list就是参数列表

函数重载:

相同的作用域中定义的函数,函数名相同,参数列表不同(可能是个数不同,也可能是对应的参数类型不同)形成重载。

不同的作用域中的相同名称函数只会形成隐藏,而不是重载。(只能调用特定的函数)

定义函数的作用域发生嵌套,内部作用域定义的函数将隐藏外围作用域中定义的函数。

类的静态成员函数和实例成员函数可以形成重载(这也意味着不能同时定义具有相同名称,相同参数列表的静态成员函数和实例成员函数)

函数重载和覆盖:

重载发生在同一作用域的两个函数之间,覆盖发生在基类成员函数和派生类成员函数之间(派生类覆盖基类)

重载的两个函数,名字相同,参数列表一定不同,返回值不限制。形成覆盖的两个函数,名称,参数列表,返回类型一定相同

类的静态成员函数和实例成员函数可以形成重载,覆盖只存在于基类的实例成员函数和派生类的实例成员函数之间。

函数调用时,重载的函数入口地址是编译时确定的(静态连编),覆盖是运行时确定的(虚函数的动态绑定机制)

如何确定具体调用哪个实际函数?

1确定重载函数集合(候选函数)2找出可行函数(参数个数相同或者多余的参数是有默认值的)

3寻找最佳匹配。

1)准备匹配 2)提升匹配 3)利用标准转换的匹配 4)利用用户定义的转换匹配 5)利用函数声明的...匹配(可变参数)

找不到可行函数或者在某一层次上有大于1个最佳匹配(二义性)时匹配失败

操作符重载

就是对操作符(+ - *  / () 等)重载,是代码更直观比如 两个类相加可以直接写成类似于数学表达式的形式 a+b;

只有C++预定义的操作符集中的操作符才可以进行重载。操作符本质等同于一个函数,由operator关键字后跟操作符组成

operator+就是加法的操作符重载。

操作符所带的操作数个数不可改变,优先级不可改变。

不能对基本数据类型定义其它操作,即对操作符重载,操作数其中之一必须为用户自定义类型或者枚举类型。

. .* ::  ?:等操作符不可以被重载

操作符重载后,可以显示调用,也可以隐式调用 如a+b和 a.operator+(b)是相同的。

类型转换操作符的重载

class A
{
public:
    A(int n){val=n;}
    void Printf()
    {
        std::cout<<"call A:"<<n<<std::endl;
    }
private:
    int val;
}

class B
{
public:
    B(int n){val=n;}
    operator A() //重载
    {
        return A(val);
    }
    void Printf()
    {
        std::cout<<"call B:"<<n<<std::endl;
    }
private:
    int val;
}

int test()
{
    B b(2);
    b.Printf();

    A a=b; //隐式调用A(b)
    a.Printf();
}

除了对()操作符外,对其他重载操作符提供参数的默认值都是非法的。

 

类的成员函数与外部函数(静态函数)的区别:

c语言中,所以函数都是外部函数。而C++由于封装的原因,函数是属于类的。类的静态成员函数在调用特性上和外部函数相同。

类的非静态成员函数与外部函数的本质区别就是非静态成员函数需要访问类的实例,因此在调用时需要传入类的实例的地址。

需要注意的几点:

1.外部函数的地址可以赋值给void* 型指针,但类的非静态成员函数则不行(如果可以的话就可以做到非静态到void*再转换为函数指针,这样就可以直接不通过类实例直接调用其非静态成员函数了,封装将毫无意义,后果极其严重!!!)

2.如果有特殊需要,可以通过嵌入汇编代码的方式(内联汇编)获取类的非静态成员函数的入口地址(非常不建议使用)

内联函数:

普通的函数(非内联函数)在被调用时,需要经历传递参数、传递返回地址、保存重要寄存器的值、函数调用、恢复重要寄存器的值、返回、清理堆栈等一系列操作。

C通过使用宏来避免函数调用,但是宏本身不是函数,只是机械的展开代码而不做语法语义检查,常常会造成一些问题。

内联函数虽然也是代码展开,但是编译器会像对普通函数一样对内联函数进行分析检查(宏是预处理器处理,内联函数是编译器处理,所以可以做的事情要多得多)

inline关键字和函数定义放在一起才能使函数内联,而在函数声明前面不起作用,一个成员函数在类的内部定义,将自动默认为内联函数。但是定义为内联函数并不意味着编译器一定会将其当做内联函数来处理,其中还包括性能方面的取舍,因为大多数情况下内联函数虽然通过省去函数调用提高了执行效率,但是也导致执行代码量的增加,所以如果函数本身就比较复杂的情况下内联可能适得其反。

下列情况将不会成为内联函数:

1.函数体内的代码比较长。

2.函数体内有循环或者递归

3将函数指针指向某个函数,这个函数也不能是内联的。

函数的返回值

一般来讲,函数的返回值应该放在一个由主函数开辟的栈空间上,但是随着计算机硬件的发展,通用寄存器的字长在增加,像函数参数的传递,返回值的传递很多时候可以通过通用寄存器来完成,不一定必须要使用计算机的存储器。

当函数返回值不超过64bit时,返回值可能会存储在各种不同的寄存器中,而不是计算机内存。

当函数返回一个对象且体积超过64bit时,主函数会在栈上创建一个临时对象,然后把这个对象的地址传给寄存器,用于返回值的传递。

 

extern “C”

extern是C/C++语言中表明函数和全局变量作用范围的关键字,该关键字告诉编译器,其声明的函数和变量可以在全局范围内使用,这种函数或者变量只能被定义一次,否则会出现链接错误。

由于C不支持重载,所以全局变量和函数可以简单在名字前加一个“_”下划线就可以生成编译所需的符号,而C++由于重载则不行,如func(int,bool) 和 func(int,int)的可能符号位func_int_bool和func_int_int(即名字改编)

所以在C和C++混编的情况下,extern C将变得很有必要。

在C++源程序调用C函数,需要在源程序中声明该函数原型,这时就需要加上extern C

直接这样使用会导致链接错误,需要把源中的函数这样声明

 

典型情况:

C++程序调用C语言的.DLL,当包含.DLL的头文件或声明接口函数时,应该是要extern C

在C中调用C++函数:

C++源文件在编译时,需要遵守C的全局变量和函数编译规范,这时就需要对C++源程序中函数和变量进行extern C修饰。

/***cpp.h***/
extern "C" //要让c可以调用在C++中的函数,需要在c++中做extern C的修饰
{
	void show();
}

/***cpp.cpp***/
void show()
{
	printf("this is a cpp function");
}

/***c.c***/
void show(); //调用C++的函数,需要在这里做声明
int main()
{
    show();
}

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值