C++基础知识(二)


十、函数重载(重载、隐藏、重写)

1、函数重载
    在同一作用域下,函数名相同,参数列表不同的函数,构成重载关系。
#include <iostream>
using namespace std;

void f(const int* p)
{
    cout<<"f const"<<endl;
}

void f(int* p)
{
    cout<<"f"<<endl;
}

int main()
{
    const int num = 0;
    f(&num);
}

2、重载实现的机制
C++代码在编译时会把函数的参数类型添加到参数名中,借助这个方式来实现函数重载,也就是C++的函数在编译期间经历换名的过程。
因此,C++代码不能调用C函数(C语言编译器编译出的函数)
3、extern “C” {}
告诉C++编译器按照C语言的方式声明函数,这样C++就可以调用C编译器编译出的函数了(C++目标文件可以与C目标文件合并生成可执行程序)。
如果C想调用C++编译出的函数,需要将C++函数的定义用extern “C”包括一下。
注意:如果两个函数名一样,一定会冲突。
4、重载和作用域
函数的重载关系发生在同一作用域下,不同作用域下的同名函数,构成隐藏关系。
5、重载解析
当调用函数时,编译器根据实参的类型和形参的匹配情况,选择一个确定的重载版本,这个过程叫重载解析。
实参的类型和形参的匹配情况有三种:
1、编译器找到与实参最佳的匹配函数,编译器将生成调用代码。
2、编译找不到匹配函数,编译器将给出错误信息。
3、编译器找到多个匹配函数,但没有一个最佳的,这种错误叫二义性。
在大多数情况下编译器都能立即找到一个最佳的调用版本,但如果没有,编译就会进行类型提升,这样备选函数中就可能具有多个可调用
的版本,这样就可能产生二义性错误。
6、确定存在函数的三个步骤
1)候选函数
函数调用的第一步就是确定所有可调用的函数的集合(函数名、作用域),该集合中的函数就是候选函数。
2)选择可行函数
从候选函数中选择一个或多个函数,选择的标准是参数个数相同,而且通过类型提升实参可被隐式转换为形参。
3)寻找最佳匹配
优先每个参数都完全匹配的方案,其次参数完全匹配的个数,再其次是浪费内存的字节数。
7、指针类型会对函数重载造成影响
C++函数的形参如果是指针类型,编译时函数名中会追加Px。

#include <iostream>
using namespace std;

void sum(short a,short b)
{
    cout<< a+b << "  3" << endl;
}

void sum(int a,short b)
{
    cout<< a+b << "  1" << endl;
}

void sum(short a,long b)
{
    cout << a+b <<"  2"<< endl;
}

int main()
{
    short a=3, b=7;
    sum(a,b);
}

十一、默认形参

1、在C++中函数的形参可以设置默认值,调用函数,如果没有提供实参数,则使用默认形参。
2、如果形参只有一部分设置了默认形参,在某个提供了默认值的参数后面,所有的参数都必须提供默认值。
3、函数的默认形参是在编译阶段确定的,因此只能使用常量、常量表达式、全局变量数据作为默认值。
4、如果函数的声明和定义需要分开,那么默认形参设置在声明、定义,还是声明定义都需要设置。
5、默认形参会对函数重载造成影响,设置默认形参时一定要慎重。
#include <iostream>
using namespace std;

void fun(int b,int a=100);

int main()
{
    fun(10,20);
    fun(50);
}


void fun(int b,int a)
{
    cout <<"b:" << b << endl;
    cout <<"a:" << a << endl;
}

十二、内联函数

1、普通函数调用时是生成调用指令(跳转),然后当代码执行到调用位置时跳转到函数所在的代码段执行。
2、内联函数就把函数编译好的二进制指令直接复制到函数的调用位置。
3、内联函数的优点就是提高程序的运行速度(因为没有跳转,也不需要返回),但这样会导致可执行文件增大(冗余),也就是牺牲空间来换取时间。
4、内联分为显示内联和隐式内联
    显示内联:在函数前 inline(C语言C99标准也支持)
    隐式内联:结构、类中内部直接定义的成员函数,则该类型函数会被优化成内联函数。
5、宏函数在调用时会把函数体直接替换到调用位置,与内联函数一样也是使用空间来换取时间,所以宏函数与内联函数的区别(优缺点)?
    1.宏函数不是真正的函数,只是代码替换,不会有参数压栈、出栈以及返回值,也不会检查参数类型,因此所有类型都能使用,但这样会有安全隐患。
    2.内联函数是真正的函数,被调用时会进行传参,会进行压栈、出栈,可以有返回值,并会严格检查参数类型,这样就不能通用,如果被多种类型调用需要重载。
6、内联适用的条件
    由于内联会造成可执行文件变大,并增加内存开销,因此只有频繁调用的简单函数适合作为内联。
    调用比较少的复杂函数,内联后并不显著提高性能,不足以抵消牺牲空间带来的损失,所以不适合内联。
    带有递归特性和动态绑定特性的函数,无法实施内联,因此编译器会忽略声明部分的inline关键字。

十三、引用

引用就是取艺名(别名)。
#include <iostream>
using namespace std;

int main()
{
    int wqq = 18;
    int& xiu = wqq;
    //int& xiu; 不存在空引用,错误
    //int const & xi = 20; 无名对象
    cout << wqq <<" "<<xiu<<endl;
}
1、引用的基本特性
    引用就是取别名,声明一个标识符为引用,就表示该标识符是另一个对象的外号。
    1)引用必须初始化,不存在空引用,但有悬空引用(变量死了,名还留着)。
    2)可以引用无名对象(临时对象),但必须使用常引用。
    3)引用不能更换目标
    引用一旦完成了定义和初始化就和普通变量名一样,它就代表了目标,一经引用终身不能再引用其他目标。
2、引用型参数
    引用当作函数的参数能达到指针同样的效果,但不具备指针的危险,还比指针方便。
    引用可以非常简单的实现函数间共享变量的目的,而且是否使用引用由被调函数说了算。
    引用当作函数的参数还能提高传递参数效率,指针至少还需要4字节内存,而引用只需要增加一条标识符与内存之间的绑定(映射)。
3、引用型返回值
    不要返回局部变量的引用,会造成悬空引用。
    如果返回值是一个临时值(右值),如果非要使用引用接收的话,必须使用常引用。
注意:C++中的引用时一种取别名的机制,而C语言中的指针是一种数据类型(代表内存编号的无符号整数)。
练习1:实现一个C++版本的swap函数。

指针和引用的相同点和不同点:
    相同点:跨函数共享变量,优化传参效率,避免传参的时候调用拷贝构造
    不同点:指针有自己的存储空间,借助指针可以使用堆内存,引用不行。引用取别名,指针是数据类型。指针可以为空,引用不可以为空。指针可以不初始化,引用必须初始化。指针可以改变指向,引用不能引用其他对象(可以定义指针的指针,不能定义引用的引用。可以定义指针的引用,不能定义引用的指针。可以定义指针的数组,但不能定义引用的数组。可以定义数组的引用)。
#include <iostream>
using namespace std;

void swap(int& a,int& b) //引用
{
    int temp = a;
    a = b;
    b = temp;
}

int main()
{
    int a=3,b=4;
    swap(a,b);
    cout<<a<<" "<<b<<endl;
}

十四、C++的内存管理

1、new/delete C++具备申请/释放堆内存功能的运算符
    相当于C语言中的malloc和free。
    new 类型:会自动计算类型所需要字节数,然后从堆中分配对应字节数的内存,并返回内存的首地址(具备类型)。
    delete 指针:会自动释放堆内存。
    注意:new/delete与malloc/free不能混用,因为new和delete会自动调用类、结构的构造函数、析构函数。
2、数组的分配与释放
    new 类型[n]; n表示数组长度,如果类、结构会自动调用n次构造函数。
    delete[] 指针;通过new[] 分配的内存,必须通过delete[]释放。
    new[] 返回值前4个字节中存放着数组的长度。
3、重复释放
    delete/delete[]不能重复释放同一块内存。
    delete/delete[]释放野指针的后果不确定,但释放空指针是安全的。
#include <iostream>
using namespace std;

struct Student
{
    Student(void)
    {
        cout<<"我是构造函数,创建对象时,我就会执行" << endl;
    }

    ~Student(void)
    {
        cout<< "我是析构函数,释放对象时,我就会执行" << endl;
    }
};

int main()
{
    int* p = new int;
    *p = 10;

    cout<< *p <<endl;

    Student stu;

    Student* s = new Student;
    delete(s);
    cout << endl;

    Student* a = new Student[3];
    p = (int*)a;
    cout << *(p-1) << endl;
    delete[] a;    
}

4、内存分配失败
当分配的内存过大,没有能满足需求的整块内存就会抛出异常,std::bad_alloc。
new/delete和C语言的malloc/free的相同点和不同点(区别)?
不同点:
身份 运算符 标准库函数
参数 类型(自动计算) 字节数(手动计算)
返回值 带类型的地址 void*地址
调用构造 自动调用 不能调用构造/析构函数
出错 抛出异常 返回NULL
相同点:
1、都能管理堆内存
2、不能重复释放
3、可以释放NULL

注意:在C++中尽量使用引用、new/delete

#include <iostream>
using namespace std;

int main()
{
    int *p = NULL;
    try{
        p = new int[~0];
    }
    catch(std::bad_alloc& ex)
    {
        cout << "error" << endl;
    }
}

Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Love丶伊卡洛斯 !
评论
 Previous
C++基础知识(三) C++基础知识(三)
一、this指针类的成员变量单独存储在每个类对象中,成员函数存储在代码段中,所有的类对象共享一份成员函数。 成员函数是如何区别调用它的是哪个类对象的? 答:借助了this指针,类的每个成员函数都有一个隐藏的参数this指针,它指向类对象。
2019-08-29
Next 
C++基础知识(一) C++基础知识(一)
思考题:C与C++的区别? 一、C++介绍本贾尼·斯特劳斯特卢普,与1979年4月份贝尔实验室的本贾尼博士在分析UNIX系统分布内核流量分析时,希望有一种有效的更加模块化的工具。 1979年10月完成了预处理器Cpre,为C增加了类机制,也
2019-08-29
  TOC