C++浓缩(三)

8、函数探幽

本章介绍的C++在C语言基础上新增的特性,比前面各章都多,这是进入C++领域的重要一步。

8.1 内联函数

是为提高程序运行速度所做的一项改进;

常规函数与内联函数的主要区别,不在于编写方式,而在于C++编译器如何将它们组合到程序中。

编译过程的最终产物--由一组机器语言指令组成。运行程序时,操作系统将这些指令载入到计算机内存中,因此每条指令都有特定的内存地址。计算机随后将逐步执行这些指令。

有时,将跳过一些指令,向前或向后跳到特定地址。常规函数调用也使程序跳到另一个地址,并在函数结束时返回。

下面更详细地介绍这一过程的典型实现:

执行到函数调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈,跳到标记函数起点的内存单元,执行函数代码,然后跳回到地址被保存的指令处。来回跳跃记录跳跃位置意味着以前使用函数时,需要一定的开销。

C++内联函数提供了另一种选择,内联函数的编译代码与其他程序代码“内联”起来了。也就是说,编译器将使用相应的函数代码替换函数调用。对于内联代码,程序无需跳到另一个位置处执行代码,再跳回来。因此,内联函数的运行速度比常规函数块,但代价是占用更多内存。如果程序在10个地方调用同一个内联函数,则该程序将包含该函数代码的10个副本。

应有选择的使用内联函数。如果执行函数代码的时间比处理函数调用机制的时间长,则节省的时间将只占整个过程的很小一部分。如果代码执行时间很短,则内联调用就可以节省非内联调用使用的大部分时间。

另一方面,由于这个过程相当快,因此尽管节省了该过程的大部分时间,但节省的时间绝对值并不大。除非该函数经常被调用。

* 函数声明前加上关键字inline;

* 函数定义前加上关键字inline;

通常做法:省略原型,将整个定义放在本应提供原型的地方。

编译器不一定会满足将函数作为内联函数的需求:

* 它可能认为函数过大或注意到函数调用了自己(内联函数不能递归),因此不能将其作为内联函数;

* 有些编译器没有启用或实现这种特性;

#include<iostream>
#include<string>
inline double sqart(double x) {
    return x * x;
}
int main() {
    using namespace std;
     double a = sqart(2);
     double b = sqart(2 + 2);
     cout << a << " " <<  b<< endl;
     return 0;
}

8.2 引用变量

引用是已定义的变量的别名(另一个名称)。主要用途用作函数的形参。通过将引用变量用作参数,函数将使用原始数据,而不是其副本。

这样,除指针之外,引用也为函数处理大型结构提供了非常方便的途径。

8.2.1 创建引用变量

前面讲,使用&符号来指示变量的地址。

C++给&赋予了另一个含义,将其用来声明引用。

int rats;
int & rodents = rats;

此处,&不是地址运算符,而是类型标识符的一部分。int & 指的是指向int的引用。

上述声明允许将rats和rodents互换---它们指向相同的值和内存单元。

#include<iostream>
#include<string>
int main() {
    using namespace std;
    int rats = 10;
    int & rodents = rats;
    cout << "rats: " << rats << endl;
    cout << "rodents: " << rodents << endl;
    rodents++;
    cout << "rats: " << rats << endl;
    cout << "rodents: " << rodents << endl;

    cout << "rats address: " << &rats << endl;
    cout << "rodents address: " << &rodents << endl;
     return 0;
}

191958_VY6d_724288.png

对于C语言用户,首次解除引用会有些困惑,因此会想到指针,但它们之间还是有区别的。

int rats = 101;
int & rodents = rats;
int * prats = &rats;

这点看,引用看上去很像伪装表示的指针。

实际上,引用还是不同于指针的。比如:必须在声明引用时将其初始化,而不能像指针那样,先声明,再赋值;

引用更接近const指针,必须在创建时进行初始化,一旦与某个变量关联起来,就将一直效忠于它。

也就是说:

int & rodents = rats;

实际上是下述代码的伪装表示:

int * const pr = &rats;

其中,引用rodents扮演的角色与表达式*pr相同。

注意:可以通过初始化声明来设置引用,但不能通过赋值来设置。

8.2.2 将引用用作函数参数

引用经常被用做函数参数,使得函数中的变量名称为调用程序中的变量的别名。这种传递参数的方法称为按引用传递。按引用传递允许被调用函数能够访问调用函数中的变量。

C语言只能按值传递,当然,C语言也允许避开按值传递的限制,采用按指针传递的方式。

案例:交换两个变量的值,

* 按值传递

* 引用

* 指针

#include<iostream>
#include<string>
void swapr(int & a, int & b);
void swapp(int * a, int * b);
void swapv(int a, int b);

int main() {
    using namespace std;
    int wallet1 = 100;
    int wallet2 = 200;
    cout << "wallet1: " << wallet1 << endl;
    cout << "wallet2: " << wallet2 << endl;

    swapr(wallet1, wallet2);
    cout << "wallet1: " << wallet1 << endl;
    cout << "wallet2: " << wallet2 << endl;

    swapp(&wallet1, &wallet2);
    cout << "wallet1: " << wallet1 << endl;
    cout << "wallet2: " << wallet2 << endl;

    swapv(wallet1, wallet2);
    cout << "wallet1: " << wallet1 << endl;
    cout << "wallet2: " << wallet2 << endl;
     return 0;
}
void swapr(int & a, int & b) {
    int temp;
    temp = b;
    b = a;
    a = temp;
}

void swapp(int * a, int * b) {
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

void swapv(int a, int b) {
    int temp;
    temp = b;
    b = a;
    a = temp;
}

194441_BAko_724288.png

其中,引用和指针都成功交换了两个钱夹的内容,而按值传递的方法没能完成这项任务。

swapr(wallet1, wallet2);
swapp(&wallet1, &wallet2);
swapv(wallet1, wallet2);

按引用传递和按值传递看起来相同。只能通过原型或函数定义才能知道swapr()是按引用传递的。

按引用传递和按值传递的代码:

  • 外在区别:声明函数参数的方式不同;

  • 内在区别:按引用传递,变量a和b是wallet1和wallet2的别名,所以交换a和b的值相当于交换wallet1和wallet2的值;

按值传递,变量a和b是复制了wallet1和wallet2的值得新变量,因此,交换a和b的值不会影响wallet1和wallet2的值;

按引用传递和指针传递的代码:

  • 声明函数参数的方式不同;

  • 指针版本需要在函数使用a和b的整个过程中使用*;

8.2.3 引用的属性和特别之处

示例:

#include<iostream>
#include<string>
double cube(double a);
double refcube(double & a);

int main() {
    using namespace std;
    double a = 10;
    cout << cube(a) << ":" << endl;
    cout << a << endl;
    cout << refcube(a) << ":" << endl;
    cout << a << endl;
      return 0;
}
double cube(double a) {
    a *= a * a;
    return a;
}
double refcube(double & a) {
    a *= a * a;
    return a;
}

refcube修改了a的值,而cube没有,这提醒我们为何通常按值传递。

若程序员的意图是让函数使用传递给它的信息,而不是对这些信息进行修改,同时又想使用引用,则应使用常量引用。

double refcube(const double &a)

另外,若要编写类似于上述示例的函数(即使用基本数值类型),应按值传递,而非引用传递;当数据比较大时(如结构和类),引用参数将很有用。

按值传递的函数,可使用多种类型的实参。

cube(x+2.0);
cube(8.0);
int k = 10;
cube(k);

如果将与上面类似的参数传递给接受引用参数的函数,将会发现,传递引用的限制更严格,毕竟,如果a是一个变量的别名,则实参应是该变量。下面的代码不合理,因为x+2.0并不是变量;

如果试图使用像refcube(x+3.0)这样的函数的调用,将发生什么情况呢?

现代C++中,这是错误的,大多数编译器都将指出这一点;而有些较老的编译器将发出这样的警告:

warning:Temporary used for parameter 'a' in call to refuce(doulbe &)

之所以有这样比较温和的反应由于早期C++确实允许将表达式传递给引用变量。

这样做的结果是:程序将创建一个临时的无名变量,并将其初始化为表达式x+2.0的值,然后,a将成为该临时变量的引用。

下面讨论这种临时变量,看看什么时候创建它们,什么时候不创建。

临时变量:引用参数和const

如果实参与引用参数不匹配,C++将生成临时变量。

当前,仅当参数为const引用时,C++才允许这样做,但以前不是这样。

下面看:

* 何种情况下,C++将生成临时变量;

* 为何对const引用的限制是合理的;

一、何时创建临时变量?如果引用参数是csont,则编译器在以下生成临时变量:

* 实参的类型正确,但不是左值;

* 实参的类型不正确,但可以转换成正确的类型

左值是什么呢?

左值是可以被引用的数据对象。非左值包括字面常量和包含多项的表达式。

C语言中,左值最初指可以出现在赋值语句左边的实体,但这是引入关键字const之前的情况;

现在,常规变量和const变量都可以视为左值,因为可以通过地址访问它们,但常规变量属于可修改的左值,而const变量属于不可修改的左值。

#include<iostream>
#include<string>
double refcube(const double & a);

int main() {
    using namespace std;
    double side = 10;
    double * ps = &side;
    double & rs = side;
    long edge = 5L;
    double len[4] = {2.0, 5.0, 10.0, 12.0};
    double c1 = refcube(side);
    double c2 = refcube(*ps);
    double c3 = refcube(rs);
    double c4 = refcube(len[2]);
    double c5 = refcube(7.0);
    double c6 = refcube(side + 10.0);
      return 0;
}

double refcube( const double & a) {
    return a * a * a;
}

参数side、len[2]、*ps、rs都有有名称、double类型的数据对象,因此可以创建引用,而不需要临时变量。

然后,edge随是变量,类型却不正确,double引用不能指向long。

另一方面,参数7.0和side+10.0 的类型都正确,但没有名称,在这些情况下,编译器都将生成一个临时匿名变量,并让a指向它。这些临时变量只在函数调用期间存在,此后编译器可以随意删除。

二、为什么对于常量引用,这种行为可行,其他情况不行呢?

void swapr(int & a, int & b) {
    int temp;
    temp = b;
    b = a;
    a = temp;
}

若在早期C++比较宽松的情况下,执行下面的操作将发生什么呢?

long a=3,b=5;
swapr(a,b);

这里类型不匹配,因此编译器将创建两个临时int变量,并将它们初始化为3和5,然后交换临时变量的内容,而a和b保持不变。

简而言之,如果接受引用参数的函数的意图是修改作为参数传递的变量,则创建临时变量将阻止这种意图的实现。

解决办法,禁止创建临时变量,现在的C++标准正式如此。

现在来看refcube函数,该函数目的仅使用传递的值,而不是修改它们,因此临时变量不会造成任何不利的影响,反而会使函数在可处理的参数种类方面更通用。

因此,若声明将引用指定为const,C++将在必要时生成临时变量。

实际上,对于形参为const引用的C++函数,若实参不匹配,则其行为类似于按值传递,为确保原始数据不被修改,将使用临时变量来存储值。

应尽可能使用const,理由:

* 使用const避免无意中修改数据的编程错误;

* cosnt使函数能处理const和非const实参,否则只能接受非const数据;

* const引用使函数能够正确生成并使用临时变量;

8.2.4 将引用用于结构

1、程序说明

#include<iostream>
#include<string>
struct free_throws{
    std::string name;
    int made;
    int attempt;
    float percent;
};
void display(const free_throws & ft);
void set_pc(free_throws & ft);
free_throws & accumulate(free_throws & target, const free_throws & source);
int main() {
    using namespace std;
     free_throws one = {"one", 13, 14};
     free_throws two = {"two", 2, 3};
     free_throws three = {"three", 4, 5};
     free_throws four = {"four", 6, 7};
     free_throws five = {"five", 8, 9};
     free_throws team = {"team", 10, 11};

     free_throws dup;
     set_pc(one);
     display(one);
     accumulate(team, one);
     display(team);

     //
     display(accumulate(team, two));
     accumulate(accumulate(team, three), four);
     display(team);
     //
     dup = accumulate(team, five);
     std::cout << "Display team: " << endl;
     display(team);
     std::cout << "Display dup: " << endl;
     display(dup);

     set_pc(four);
     accumulate(dup, five) = four;

     cout << "Display dup: " << endl;
     display(dup);

      return 0;
}

void display(const free_throws & ft) {
    using namespace std;
    cout << "name: " << ft.name << endl;
    cout << "made: " << ft.made << endl;
    cout << "attempt: " << ft.attempt << endl;
    cout << "percent: " << ft.percent << endl;
}
void set_pc(free_throws & ft){
    if (ft.attempt != 0)
        ft.percent = 100.0f * float(ft.made)/float(ft.attempt);
    else
        ft.percent = 0;
}
free_throws & accumulate(free_throws & target, const free_throws & source) {
    target.attempt += source.attempt;
    target.made += source.made;
    set_pc(target);
    return target;
}

初始化了多个结构对象。若指定的初始值比成员少,余下的成员将被设置为0;

display(one);

此处函数使用const引用参数,不修改它。

就这个函数而言,使用按值传递也可,但与复制原始结构的拷贝相比,使用引用可节省时间和内存。

注意此处代码:display(accumulate(team, two));

* 将结构对象team作为第一个参数传给了accumulate,accumulate返回指向它的引用;

* 接下来,将accumulate返回值作为参数传递给display,意味着team传递给了display,display的参数为引用,因此ft指向的是team;

程序以独特的方式使用了accumulate:

accumulate(dup, five) = four;

这条语句将值赋给函数调用,这是可行的,因为函数的返回值是一个引用。

如果accumulate按值返回,这条语句不能通过编译。

2、为何要返回引用

返回值若传统按值传递,这个值被复制到一个临时位置,而调用程序将使用这个值。

dup = accumulate(team, five);

* 若返回一个结构,而不是结构的引用,将把整个结构复制到一个临时位置,再将这个拷贝赋值给dup;

* 但在引用时,将直接把team复制到dup,其效率更高;

3、返回引用时需要注意的问题

返回引用时最重要的一点:避免返回函数终止时不再存在的内存单元引用。

避免编写下面这样的代码:

const free_throws & clone2(free_throws & ft) {
     free_throws newguy;
     newguy = ft;
     return newguy;
}

该函数返回一个指向临时变量的引用,函数运行完毕后不再存在。

为避免这个问题:最简单的办法,返回一个作为参数传递给函数的引用。作为参数的引用将指向调用函数使用的数据,因此返回的引用也将指向这些数据。

另一个办法:使用new来分配新的存储空间。前面见过,使用new为字符串分配内存空间,并返回指向该内存空间的指针。

const free_throws & clone(free_throws & ft) {
     free_throws * pt;
     *pt = ft;// 创建一个无名的free_throws结构,并让指针pt指向该结构,因此*pt就是该结构
     return *pt;// 似乎会返回该结构,但函数声明表明,该函数实际上将返回这个结构的引用。
}
free_throws & jolly = clone(three);

这样jolly称为新结构的引用。

存在的问题:在不再需要new分配内存时,应使用delete来释放它们。调用clone隐藏了对new的调用,这使得以后很容易忘记使用delete来释放内存。

第16张的auto_ptr模块以及C++11的unique_ptr可以帮助程序员自动完成释放工作。

4、为何将const用于引用返回类型

accumulate(dup, five) = four;

效果如下:首先将five的数据添加到dup中,再使用four的内容覆盖dup的内容。

(1)在赋值语句中,左边必须是可修改的左值。意味着,在赋值表达式中,左边的子表达式必须标识一个可修改的内存块。在这里,函数返回指向dup的引用,它缺失标识这样的一个内存块。因此合法。

(2)另一方面,常规返回类型是右值--不能通过地址访问。这种表达式可出现在赋值语句的右边,但不能出现在左边。其他右值包括字面值和表达式。

显然,获取字面值得地址没有任何意义,但为何常规函数返回值是右值呢?因为这种返回值位于临时内存单元中,运行到下一条语句时,它们可能不再存在。

假设您要使用返回值,但又不允许指向像accumulate赋值这样的操作,只需将返回类型声明为const引用。

const free_throws & accumulate(free_throws & target, const free_throws & source)

这样,下面的赋值语句就不合法了。

accumulate(dup, five) = four;

通过省略const,可以编写代码简短,但其含义也更模糊。

8.2.5 将引用用于类对象
#include<iostream>
#include<string>
using namespace std;
string version1(const string & s1, const string & s2);
const string & version2(string & s1, const string & s2);
const string & version3(string & s1, const string & s2);
int main() {
    string input;
    string copy;
    string result;
    cout << "Enter the string: " << endl;
    getline(cin, input);
    copy = input;
    cout << "Your string has entered: " << input << endl;

    result = version1(input, "***");
    cout << "Your string has enhanced: " << result << endl;
    cout << "Your origal string: " << input <<endl;

    result = version2(input, "***");
    cout << "Your string has enhanced: " << result << endl;
    cout << "Your origal string: " << input <<endl;

    cout << "Reset origal string:" << endl;
    input = copy;
    result = version3(input, "***");
    cout << "Your string has enhanced: " << result << endl;
    cout << "Your origal string: " << input <<endl;

    return 0;
}

string version1(const string & s1, const string & s2) {
    string temp;
    temp = s1 + s2 + s1;
    return temp;
}
const string & version2(string & s1, const string & s2) {
    s1 = s1 + s2 + s1;
    return s1;
}
const string & version3(string & s1, const string & s2) {
    string temp;
    temp = s1 + s2 + s1;
    return temp;
}

运行该程序,会崩溃;(程序崩溃的原因:返回一个指向version3中声明的变量的引用。这个函数能够通过编译,但执行时将崩溃)

将C-风格字符串用作string对象引用参数:

实参input和“***”的类型分别是string和const char*。程序怎么能够接受将char指针赋给string引用呢?

一、string类定义了一种char*到string的转换功能,使得可以使用C-风格字符串来初始化string对象。

二、const引用的形参的一个属性。若实参的类型与引用参数类型不匹配,但可被转换为引用类型,将创建一个临时变量,使用转换后的实参值来初始化它,然后传递一个指向该临时变量的引用。

这种属性的结果是:若形参类型为const string &,在调用函数时,使用的实参可以是string对象或C-风格字符串,如用引号扩起来的字符串字面量、以空字符结尾的char数组或指向char的指针变量。

8.2.6 对象、继承和引用

ofstream对象可以使用ostream类的方法,使得能够将特性从一个类传递给另一个类的语言特性称为继承。

ostream是基类,ofstream是派生类。派生类继承了基类的方法。

继承的另一个特性:基类引用可以指向派生类对象,而无需进行强制类型转换。

程序示例:

#include<iostream>
#include<string>
#include<fstream>
#include<cstdlib>
using namespace std;
void file_it(ostream & os, double fo, const double fe[], int n);
const int LIMIT = 5;
int main() {
    ofstream fout;
    const char * fn = "a.log";
    fout.open(fn);
    if (!fout.is_open()) {
        cout << "open file fail!" << endl;
        exit(EXIT_FAILURE);
    }
    double objective;
    cout << "enter the focal length of your telescope objective in mm:" << endl;
    cin >> objective;

    double eps[LIMIT];
    cout << "enter the focal length of" << LIMIT
            << "eyepieces: " << endl;
    for (int i = 0; i < LIMIT; i++) {
        cin >> eps[i];
    }
    file_it(fout, objective, eps, LIMIT);
    file_it(cout, objective, eps, LIMIT);
    return 0;
}
void file_it(ostream & os, double fo, const double fe[], int n) {
    ios_base::fmtflags initial;
    initial = os.setf(ios_base::fixed);
    os.precision(0);
    os << "Focal length of objective: " << fo << "mm\n";
    os.setf(ios::showpoint);
    os.precision(1);
    os.width(2);
    os << "f.l. eyepiecs";
    os.width(15);
    os << "magnification" << endl;
    for(int i = 0; i < n; i++)
    {
         os.width(12);
         os << fe[i];
         os.width(15);
         os << int (fo/fe[i] + 0.5) << endl;
    }
    os.setf(initial);
}

方法self能够设置各种格式化状态。例如:

* os.setf(ios_base::fixed);将对象置于使用定点表示法的模式;

* os.setf(ios::showpoint);将对象置于显示小数点的模式,即使小数部分未零。

* precision指定显示多少位小数(假定对象除于定点模式下)

所有这些设置将一直保持不变,直到再次调用相应的方法重置它们。

方法width设置下一次输出操作使用的字段宽度,这种设置只在显示下一个值时有效,然后恢复到默认设置。默认宽度为零。

另外:

ios_base::fmtflags initial;
initial = os.setf(ios_base::fixed);
......
os.setf(initial);

方法self返回调用它之前有效的所有格式化设置。ios_base::fmtflags是存储这种信息所需的数据类型名称。

因此,将返回值赋给initial将存储调用file_it之前的格式化设置,然后便可以使用变量initial作为参数来调用self,将所有的格式化设置恢复到原来的值。

8.2.7 何时使用引用参数

使用引用参数的两个主要原因:

* 能够修改调用函数中的数据对象;

* 提高程序的运行速度

引用参数实际上是基于指针的代码的另一个接口。那么,什么时候使用引用、指针、按值传递呢?

对于使用传递的值而不作修改的函数:

  • 如果数据对象很小,如内置数据类型或小型结构,则按值传递

  • 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针。

  • 如果数据对象是较大的结构,则使用const指针或const引用,以提高程序的效率。这样可以节省复制结构的时间和空间。

  • 如果数据对象 是类对象,则使用const引用。类设计的语义常常要求使用引用,这是c++新增这项特性的主要原因。因此,传递类对象参数的标准方式 是引用传递。

对于修改调用函数中数据的函数。

  • 如果数据对象是内置数据类型,则使用指针。如果看到诸如fixit(&x)这样的代码(其中x是int型),则很明显,该函数将修改x.

  • 如果数据对象是数组,则只能使用指针。

  • 如果数据对象是结构,则使用引用或指针。

  • 如果数据对象是类对象,则使用引用。

8.3 默认参数

默认参数指定是当函数调用中省略了实参时自动使用的一个值。

如何设置默认值?通过函数原型,编译器通过查看原型来了解函数所使用的参数数目,因此函数原型也必须将可能的默认参数告知程序。方法是将值赋给原型中的参数。

例如:

char * left(const char * str,int n=1);

* 希望返回一个新的字符串,因此返回类型为char *(指向char的指针);

* 原始字符串不变,因此使用const;

* 希望n的默认值为1;

默认值是初始化值,因此上面的原型将n初始化为1。如果省略n,则n为1,否则,覆盖1。

注意:

* 对于带参数列表的函数,必须从右向左添加默认值。也就是说,要为某个参数设置默认值,则必须为它右边额所有参数提供默认值;

* 只有原型定义了默认值。函数定义与没有默认参数是完全相同;

#include<iostream>
#include<string>
using namespace std;
const int ArrSize = 80;
char * left(const char arr[] ,int n=1);
int main() {
    char charr[ArrSize];
    cout << "enter the string: " << endl;
    cin.getline(charr, ArrSize);
    char * ps = left(charr, 4);
    cout << ps << endl;
    delete[] ps;
    return 0;
}
char * left(const char arr[] ,int n) {
    if (n < 0)
        n = 0;
    char * p = new char[n+1];
    int i;
    for (i = 0; i < n && arr[i]; i++)
        p[i] = arr[i];
    while(i <= n) {
        p[i] = '\0';
        i++;
    }
    return p;
}

8.4 函数重载

术语 “多态”指的是有多种形式,因此函数多态允许函数可以有多种形式;

术语“函数重载”指的是可以有多个同名的函数,因此对名称进行了重载;

这两个术语指的是同一回事,但我们通常使用函数重载。

函数重载的关键是函数的参数列表--也成为函数特征标。

如果两个涵涵素的参数数目和类型相同。同时参数的排列顺序也相同,则它们的特征标相同,而变量名是无关紧要的。

示例:

void print(const char * str, int width)
void print(double d, int width)
void print(long l, int width)
void print(int i, int width)
void print(const char * str)

使用print函数时,编译器将根据所采取的用法使用有相应特征标的原型:

使用被重载的函数时,需要在函数调用中使用正确的参数类型。

例如:

usigned int year = 2001;
print(year,6);

print与哪个原型匹配呢?

它不与任何原型匹配,没有匹配的原型并不会停止使用其中的某个函数,因为C++将尝试使用标准类型转换强制进行匹配。

* 若只有#2是唯一的原型,则函数调用print(year,6)将把year转换为double类型;

* 但实际上,有3个将数字作为第一个参数的原型,因此有3中转换year的方式,这种情况下,视为错误;

一些看起来彼此不同的特征标识不能共存的。eg:

double cube(double x);
double cube(double & x);

从编译器角度考虑,cobe(x)有两个原型都匹配,因此编译器无法确定究竟是应使用哪个原型,为避免这种混乱,编译器检查函数特征标时,将把类型引用和类型本身视为同一个特征标。

匹配函数时,并不区别const和非const变量:





转载于:https://my.oschina.net/cqlcql/blog/645756

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值