精华内容
下载资源
问答
  • 一、对于带参数的构造函数在定义对象必须给构造函数传递参数,否则构造函数将不被执行。  但是实际应用中,有些构造函数的参数值通常是不变的,只有特殊情况下才需要改变它的参数值,这时可以定义成带参数...

    缺省参数的构造函数

    一、对于带参数的构造函数,在定义对象时必须给构造函数传递参数,否则构造函数将不被执行。

            但是在实际应用中,有些构造函数的参数值通常是不变的,只有在特殊情况下才需要改变它的参数值,这时可以定义成带缺省参数的构造函数。

    示例:

    class Stu
    {
        private:
        char *name,*stu_no;
        float score;
        public:
        Stu(char *name1,char *stu_no1,float score1);
        ~Stu();
        void modify(float score1);
        void show();
        void Show_One(int n);
    };
    
    Stu::Stu(char *name1="Mr.all",char *stu_no1="123456",float score1=100.0)
    {
        name=new char[N];
        strcpy(name,name1);
        stu_no=new char[N];
        strcpy(stu_no,stu_no1);
        score=score1;
    }
    
    Stu a("Mr.zhang","1206655023",150);
    Stu b("Mr.zhou","1206655022");
    Stu c("Mr.wu");Stu d;


    缺省的构造函数

    一、编译系统自动生成的缺省的构造函数。

          在实际的应用中,一般都是需要构造函数的。 如果没有给类定义构造函数,则编译会自动生成一个缺省的构造函数。

          格式:  类名::缺省构造函数名()    {     }

    示例:

    //类 Date中没有自定义构造函数.
    Date date1,date2;
    //编译系统会自动生成下述的构造函数
    Date::Date() { }

    它的作用:只是为对象开辟一个存储空间,而不能给对象中的数据成员赋初始值。


    Ps:

    第一,对于没有定义构造函数的类,其公有数据成员可以用初始值表进行初始化。(常用于结构和数组的初始化)

    示例:

    #include<iostream>
    
    using namespace std;
    
    class A
    {
        public:
        int a,b;
    };
    
    int main()
    {
        A s={1,2};
        //这是的A类数据成员的公有的.所以类外是可以访问的.
        /*s.a=1;
        s.b=2;*/
        cout<<s.a<<' '<<s.b<<endl;
        return 0;
    }

    第二、与定义变量类似,在用缺省的构造函数创建对象是,如果创建的是全局对象或静态对象,则对象的所有数据成员初始化为0或者空,否则对象的数据成员值的随机的。

    示例1:(公有数据成员)

    #include<iostream>
    
    using namespace std;
    
    const int N=20;
    class A
    {
        public:
        char name[N];
        int no;
    } a;
    int main()
    {
        cout<<a.name<<a.no<<endl;
        A b;
        cout<<b.name<<b.no<<endl;
        return 0;
    }

    示例2 :(私有数据成员)

    #include<iostream>
    
    using namespace std;
    
    const int N=20;
    class A
    {
        private:
        char name[N];
        int no;
        public:
        void Get()
        {cout<<name<<no<<endl;}
    } a;
    int main()
    {
        a.Get();
        A b;
        b.Get();
        return 0;
    }


    第三,只要一个类定义了一个构造函数(不一定的无参构造函数),系统将不在提供缺省的构造函数。


    缺省的析构函数

    一、每个类必须有一个析构函数。如果没有显式地为一个类定义析构函数。编译系统会自动生成一个缺省的析构函数。

            格式:类名::~析构函数名()  {    }

    示例:

    //类 Date没有显式的定义析构函数
    Date::~Date()
    {  }

    二、对于大多数的类,缺省的析构函数就能满足要求。但是,如果在一个对象完成其操作之前需要做一些内部处理,则应该显式地定义析构函数。

    展开全文
  • 构造函数能初始化对象,而缺省构造函数可以不利用任何建立对象的外部数据就能初始化对象。有时这样的方法是不错的。例如一些行为特性与数字相仿的对象被初始化为空值或不确定的值也是合理的,还有比如链表、...

    Item M4:避免无用的缺省构造函数


    缺省构造函数(指没有参数的构造函数)在C++语言中是一种让你无中生有的方法。构造函数能初始化对象,而缺省构造函数则可以不利用任何在建立对象时的外部数据就能初始化对象。有时这样的方法是不错的。例如一些行为特性与数字相仿的对象被初始化为空值或不确定的值也是合理的,还有比如链表、哈希表、图等等数据结构也可以被初始化为空容器。


    但不是所有的对象都属于上述类型,对于很多对象来说,不利用外部数据进行完全的初始化是不合理的。比如一个没有输入姓名的地址簿对象,就没有任何意义。在一些公司里,所有的设备都必须标有一个公司ID号码,所以在建立对象以模型化一个设备时,不提供一个合适的ID号码,所建立的对象就根本没有意义。

    在一个完美的世界里,无需任何数据即可建立对象的类可以包含缺省构造函数,而需要数据来建立对象的类则不能包含缺省构造函数。唉!可是我们的现实世界不是完美的,所以我们必须考虑更多的因素。特别是如果一个类没有缺省构造函数,就会存在一些使用上的限制。

    请考虑一下有这样一个类,它表示公司的设备,这个类包含一个公司的ID代码,这个ID代码被强制做为构造函数的参数:
    class EquipmentPiece {
    public:
      EquipmentPiece(int IDNumber);
      ...
    };
    
    因为EquipmentPiece类没有一个缺省构造函数,所以在三种情况下使用它,就会遇到问题。第一中情况是建立数组时。一般来说,没有一种办法能在建立对象数组时给构造函数传递参数。所以在通常情况下,不可能建立EquipmentPiece对象数组:

    EquipmentPiece bestPieces[10];           // 错误!没有正确调用
                                             // EquipmentPiece 构造函数
     EquipmentPiece *bestPieces =
      new EquipmentPiece[10];                // 错误!与上面的问题一样
    
    不过还是有三种方法能回避开这个限制。对于使用非堆数组(non-heap arrays)(即不在堆中给数组分配内存。译者注)的一种解决方法是在数组定义时提供必要的参数:
    int ID1, ID2, ID3, ..., ID10;            // 存储设备ID号的
                                             // 变量
    ... 
    EquipmentPiece bestPieces[] = {          // 正确, 提供了构造
      EquipmentPiece(ID1),                   // 函数的参数
      EquipmentPiece(ID2),
      EquipmentPiece(ID3),
      ...,
      EquipmentPiece(ID10)
    };
    

    不过很遗憾,这种方法不能用在堆数组(heap arrays)的定义上。

    一个更通用的解决方法是利用指针数组来代替一个对象数组
    typedef EquipmentPiece* PEP;             //  PEP 指针指向
                                             //一个EquipmentPiece对象
     
    PEP bestPieces[10];                      // 正确, 没有调用构造函数
    PEP *bestPieces = new PEP[10];           // 也正确
    

    指针数组里的每一个指针被重新赋值,以指向一个不同的EquipmentPiece对象:
    for (int i = 0; i < 10; ++i)
      bestPieces[i] = new EquipmentPiece( ID Number );
    

    不过这中方法有两个缺点,第一你必须删除数组里每个指针所指向的对象。如果你忘了,就会发生内存泄漏。第二增加了内存分配量,因为正如你需要空间来容纳EquipmentPiece对象一样,你也需要空间来容纳指针。

    如果你为数组分配raw memory,你就可以避免浪费内存。使用placement new方法(参见条款M8)在内存中构造EquipmentPiece对象:

    // 为大小为10的数组 分配足够的内存
    // EquipmentPiece 对象; 详细情况请参见条款M8
    // operator new[] 函数
    void *rawMemory =
      operator new[](10*sizeof(EquipmentPiece));
    // make bestPieces point to it so it can be treated as an
    // EquipmentPiece array
    EquipmentPiece *bestPieces =
      static_cast<EquipmentPiece*>(rawMemory);
    // construct the EquipmentPiece objects in the memory
    // 使用"placement new" (参见条款M8)
    for (int i = 0; i < 10; ++i)
      new (&bestPieces[i]) EquipmentPiece( ID Number );
    

    注意你仍旧得为每一个EquipmentPiece对象提供构造函数参数。这个技术(和指针数组的主意一样)允许你在没有缺省构造函数的情况下建立一个对象数组。它没有绕过对构造函数参数的需求,实际上也做不到。如果能做到的话,就不能保证对象被正确初始化。

    使用placement new的缺点除了是大多数程序员对它不熟悉外(能使用它就更难了),还有就是当你不想让它继续存在使用时,必须手动调用数组对象的析构函数,然后调用操作符delete[]来释放raw memory(请再参见条款M8):(WQ加注,已经有placement delete/delete []操作符了,它会自动调用析构函数。)

    // 以与构造bestPieces对象相反的顺序
    // 解构它。
    for (int i = 9; i >= 0; --i)
      bestPieces[i].~EquipmentPiece(); 
    // deallocate the raw memory
    operator delete[](rawMemory);
    

    如果你忘记了这个要求而使用了普通的数组删除方法,那么你程序的运行将是不可预测的。这是因为:直接删除一个不是用new操作符来分配的内存指针,其结果没有被定义。
    delete [] bestPieces;                    // 没有定义! bestPieces
                                        //不是用new操作符分配的。
    

    有关new、placement new和它们如何与构造函数、析构函数一起使用的更多信息,请见条款M8。

    对于类里没有定义缺省构造函数所造成的第二个问题是它们无法在许多基于模板(template-based)的容器类里使用。因为实例化一个模板时,模板的类型参数应该提供一个缺省构造函数,这是一个常见的要求。这个要求总是来自于模板内部,被建立的模板参数类型数组里。例如一个数组模板类:

    template<class T>
    class Array {
    public:
      Array(int size);
      ... 
    private:
      T *data;
    }; 
    template<class T>
    Array<T>::Array(int size)
    {
      data = new T[size];                    // 为每个数组元素 
      ...                                    //依次调用 T::T()
    }
    

    在多数情况下,通过仔细设计模板可以杜绝对缺省构造函数的需求。例如标准的vector模板(生成一个类似于可扩展数组的类)对它的类型参数没有必须有缺省构造函数的要求。不幸的是,很多模板类没有以仔细的态度去设计。这样,没有缺省构造函数的类就不能与许多模板兼容。当C++程序员深入领会了模板设计以后,这样的问题应该不再那么突出了。这会花多长时间,完全在于个人的造化。

    最后讲一下在设计虚基类时所面临的要提供缺省构造函数还是不提供缺省构造函数的两难决策。不提供缺省构造函数的虚基类,很难与其进行合作。因为几乎所有的派生类在实例化时都必须给虚基类构造函数提供参数。这就要求所有由没有缺省构造函数的虚基类继承下来的派生类(无论有多远)都必须知道并理解提供给虚基类构造函数的参数的含义。派生类的作者是不会企盼和喜欢这种规定的。

    因为这些强加于没有缺省构造函数的类上的种种限制,一些人认为所有的类都应该有缺省构造函数,即使缺省构造函数没有足够的数据来完整初始化一个对象。比如这个原则的拥护者会这样修改EquipmentPiece类:

    class EquipmentPiece {
    public:
      EquipmentPiece(  int IDNumber = UNSPECIFIED);
      ...
    private:
      static const int   UNSPECIFIED;        // 其值代表ID值不确定。
    };  
    

    这允许这样建立EquipmentPiece对象
    EquipmentPiece e;                         //这样合法

    这样的修改使得其他成员函数变得复杂,因为不再能确保EquipmentPiece对象进行了有意义的初始化。假设它建立一个因没有ID而没有意义的EquipmentPiece对象,那么大多数成员函数必须检测ID是否存在。如果不存在ID,它们将必须指出怎么犯的错误。不过通常不明确应该怎么去做,很多代码的实现什么也没有提供:只是抛出一个异常或调用一个函数终止程序。当这种情形发生时,很难说提供缺省构造函数而放弃了一种保证机制的做法是否能提高软件的总体质量。

    提供无意义的缺省构造函数也会影响类的工作效率。如果成员函数必须测试所有的部分是否都被正确地初始化,那么这些函数的调用者就得为此付出更多的时间。而且还得付出更多的代码,因为这使得可执行文件或库变得更大。它们也得在测试失败的地方放置代码来处理错误。如果一个类的构造函数能够确保所有的部分被正确初始化,所有这些弊病都能够避免。缺省构造函数一般不会提供这种保证,所以在它们可能使类变得没有意义时,尽量去避免使用它们。使用这种(没有缺省构造函数的)类的确有一些限制,但是当你使用它时,它也给你提供了一种保证:你能相信这个类被正确地建立和高效地实现。

    Item M5:谨慎定义类型转换函数

    C++编译器能够在两种数据类型之间进行隐式转换(implicit conversions),它继承了C语言的转换方法,例如允许把char隐式转换为int和从short隐式转换为double。因此当你把一个short值传递给准备接受double参数值的函数时,依然可以成功运行。C中许多这种可怕的转换可能会导致数据的丢失,它们在C++中依然存在,包括int到short的转换和double到char的转换。

    你对这些类型转换是无能为力的,因为它们是语言本身的特性。不过当你增加自己的类型时,你就可以有更多的控制力,因为你能选择是否提供函数让编译器进行隐式类型转换。

    有两种函数允许编译器进行这些的转换:单参数构造函数(single-argument constructors)和隐式类型转换运算符单参数构造函数是指只用一个参数即可以调用的构造函数。该函数可以是只定义了一个参数,也可以是虽定义了多个参数但第一个参数以后的所有参数都有缺省值。以下有两个例子:

    class Name {                                 // for names of things
    public:
      Name(const string& s);                     // 转换 string 到
                                                 // Name
      ...  
    }; 
    class Rational {                             // 有理数类
    public:
      Rational(int numerator = 0,                // 转换int到
               int denominator = 1);             // 有理数类
      ... 
    };
    

    注:关于转换构造函数和隐式类型转换运算符,请参考<http://blog.csdn.net/qianqin_2014/article/details/51316461>

    隐式类型转换运算符只是一个样子奇怪的成员函数:operator 关键字,其后跟一个类型符号。你不用定义函数的返回类型,因为返回类型就是这个函数的名字。例如为了允许Rational(有理数)类隐式地转换为double类型(在用有理数进行混合类型运算时,可能有用),你可以如此声明Rational类:

    class Rational {
    public:
      ...
      operator double() const;                   // 转换Rational类成
    };                                           // double类型
    

    在下面这种情况下,这个函数会被自动调用:
    Rational r(1, 2);                            // r 的值是1/2
     
    double d = 0.5 * r;                          // 转换 r 到double,
                                             // 然后做乘法
    

    以上这些说明只是一个复习,我真正想说的是为什么你不需要定义各种类型转换函数

    根本问题是当你在不需要使用转换函数时,这些函数却会被调用运行。结果,这些不正确的程序会做出一些令人恼火的事情,而你又很难判断出原因。

    让我们首先分析一下隐式类型转换运算符,它们是最容易处理的。假设你有一个如上所述的Rational类,你想让该类拥有打印有理数对象的功能,就好像它是一个内置类型。因此,你可能会这么写:

    Rational r(1, 2); 
    cout << r;                                    // 应该打印出"1/2"
    

    再假设你忘了为Rational对象定义operator<<。你可能想打印操作将失败,因为没有合适的的operator<<被调用。但是你错了。当编译器调用operator<<时,会发现没有这样的函数存在,但是它会试图找到一个合适的隐式类型转换顺序以使得函数调用正常运行。类型转换顺序的规则定义是复杂的,但是在现在这种情况下,编译器会发现它们能调用Rational::operator double函数来把r转换为double类型。所以上述代码打印的结果是一个浮点数,而不是一个有理数。这简直是一个灾难,但是它表明了隐式类型转换的缺点:它们的存在将导致错误的发生。

    解决方法是用不使用语法关键字的等同的函数来替代转换运算符。例如为了把Rational对象转换为double,用asDouble函数代替operator double函数:

    class Rational {
    public:
      ...
      double asDouble() const;                   //转变 Rational
    };                                       // 成double
    

    这个成员函数能被显式调用:

    Rational r(1, 2);
     
    cout << r;                             // 错误! Rationa对象没有
                                         // operator<< 
    cout << r.asDouble();                   // 正确, 用double类型 
                                        //打印r
    

    在多数情况下,这种显式转换函数的使用虽然不方便,但是函数被悄悄调用的情况不再会发生,这点损失是值得的。一般来说,越有经验的C++程序员就越喜欢避开类型转换运算符。例如在C++标准库(参见Effective C++条款49和M35)委员会工作的人员是在此领域最有经验的,他们加在库函数中的string类型没有包括隐式地从string转换成C风格的char*的功能,而是定义了一个成员函数c_str用来完成这个转换,这是巧合么?我看不是。

    通过单参数构造函数进行隐式类型转换更难消除。而且在很多情况下这些函数所导致的问题要甚于隐式类型转换运算符。

    举一个例子,一个array类模板,这些数组需要调用者确定边界的上限与下限:
    template<class T>
    class Array {
    public:
      Array(int lowBound, int highBound);
      Array(int size); 
      T& operator[](int index); 
      ... 
    };
    

    第一个构造函数允许调用者确定数组索引的范围,例如从10到20。它是一个两参数构造函数,所以不能做为类型转换函数。第二个构造函数让调用者仅仅定义数组元素的个数(使用方法与内置数组的使用相似),不过不同的是它能做为类型转换函数使用,能导致无穷的痛苦。

    例如比较Array<int>对象,部分代码如下:
    bool operator==( const Array<int>& lhs,
                     const Array<int>& rhs); 
    Array<int> a(10);
    Array<int> b(10); 
    ... 
    for (int i = 0; i < 10; ++i)
      if (a == b[i]) {               // 哎呦! "a" 应该是 "a[i]"
        do something for when
        a[i] and b[i] are equal;
      }
      else {
        do something for when they're not;
      }
    

    我们想用a的每个元素与b的每个元素相比较,但是当录入a时,我们偶然忘记了数组下标。当然我们希望编译器能报出各种各样的警告信息,但是它根本没有。因为它把这个调用看成用Array<int>参数(对于a)和int(对于b[i])参数调用operator==函数,然而没有operator==函数是这样的参数类型,我们的编译器注意到它能通过调用Array<int>构造函数能转换int类型到Array<int>类型,这个构造函数只有一个int类型的参数。然后编译器如此去编译,生成的代码就象这样:
    for (int i = 0; i < 10; ++i)
      if (a == static_cast< Array<int> >(b[i]))   ...
    

    每一次循环都把a的内容与一个大小为b[i]的临时数组(内容是未定义的)比较。这不仅不可能以正确的方法运行,而且还是效率低下的。因为每一次循环我们都必须建立和释放Array<int>对象(见条款M19)。

    通过不声明运算符(operator)的方法,可以克服隐式类型转换运算符的缺点,但是单参数构造函数没有那么简单。毕竟,你确实想给调用者提供一个单参数构造函数。同时你也希望防止编译器不加鉴别地调用这个构造函数。幸运的是,有一个方法可以让你鱼肉与熊掌兼得。事实上是两个方法:一是容易的方法,二是当你的编译器不支持容易的方法时所必须使用的方法。

    容易的方法是利用一个最新编译器的特性,explicit关键字。为了解决隐式类型转换而特别引入的这个特性,它的使用方法很好理解。构造函数用explicit声明,如果这样做,编译器会拒绝为了隐式类型转换而调用构造函数。显式类型转换依然合法:
    template<class T>
    class Array {
    public:
      ...
      explicit Array(int size);           // 注意使用"explicit"
      ...
    }; 
    Array<int> a(10);                 // 正确, explicit 构造函数
                                   // 在建立对象时能正常使用
    Array<int> b(10);                // 也正确 
    if (a == b[i]) ...                   // 错误! 没有办法
                                   // 隐式转换
                                   // int 到 Array<int> 
    if (a == Array<int>(b[i])) ...        // 正确,显式从int到
                                   // Array<int>转换
                                   // (但是代码的逻辑
                                   // 不合理) 
    if (a == static_cast< Array<int> >(b[i])) ...
                                   // 同样正确,同样
                                   // 不合理 
    if (a == (Array<int>)b[i]) ...        //C风格的转换也正确,
                                   // 但是逻辑
                                   // 依旧不合理
    

    在例子里使用了static_cast(参见条款M2),两个“>”字符间的空格不能漏掉,如果这样写语句:

    if (a == static_cast<Array<int>>(b[i])) ...

    这是一个不同的含义的语句。因为C++编译器把“>>”做为一个符号来解释。在两个“>”间没有空格,语句会产生语法错误。

    如果你的编译器不支持explicit,你不得不回到不使用成为隐式类型转换函数的单参数构造函数。

    我前面说过复杂的规则决定哪一个隐式类型转换是合法的,哪一个是不合法的。这些规则中没有一个转换能够包含用户自定义类型(调用单参数构造函数或隐式类型转换运算符)。你能利用这个规则来正确构造你的类,使得对象能够正常构造,同时去掉你不想要的隐式类型转换。

    再来想一下数组模板,你需要用整形变量做为构造函数参数来确定数组大小,但是同时又必须防止从整数类型到临时数组对象的隐式类型转换。你要达到这个目的,先要建立一个新类ArraySize。这个对象只有一个目的就是表示将要建立数组的大小。你必须修改Array的单参数构造函数,用一个ArraySize对象来代替int。代码如下:

    template<class T>
    class Array {
    public: 
      class ArraySize {                    // 这个类是新的
      public:
        ArraySize(int numElements): theSize(numElements) {}
        int size() const { return theSize; } 
      private:
        int theSize;
      }; 
    Array(int lowBound, int highBound);
      Array(ArraySize size);                  // 注意新的声明 
    ... 
    };
    

    这里把ArraySize嵌套入Array中,为了强调它总是与Array一起使用。你也必须声明ArraySize为公有,为了让任何人都能使用它。

    想一下,当通过单参数构造函数定义Array对象,会发生什么样的事情:
    Array<int> a(10);

    你的编译器要求用int参数调用Array<int>里的构造函数,但是没有这样的构造函数。编译器意识到它能从int参数转换成一个临时ArraySize对象,ArraySize对象只是Array<int>构造函数所需要的,这样编译器进行了转换。函数调用(及其后的对象建立)也就成功了。

    事实上你仍旧能够安心地构造Array对象,不过这样做能够使你避免类型转换。考虑一下以下代码:

    bool operator==( const Array<int>& lhs,
                     const Array<int>& rhs);
    Array<int> a(10);
    Array<int> b(10);
    ...
    for (int i = 0; i < 10; ++i)
      if (a == b[i]) ...                 // 哎呦! "a" 应该是 "a[i]";
                                   // 现在是一个错误。
    

    为了调用operator==函数,编译器要求Array<int>对象在”==”右侧,但是不存在一个参数为int的单参数构造函数而且编译器无法把int转换成一个临时ArraySize对象然后通过这个临时对象建立必须的Array<int>对象,因为这将调用两个用户定义(user-defined)的类型转换,一个从int到ArraySize,一个从ArraySize到Array<int>。这种转换顺序被禁止的,所以当试图进行比较时编译器肯定会产生错误。

    ArraySize类的使用有些象一个有目的的帮手,这是一个更通用技术的应用实例。类似于ArraySize的类经常被称为proxy classes(代理类),因为这样类的每一个对象都为了支持其他对象的工作。ArraySize对象实际是一个整数类型的替代者,用来在建立Array对象时确定数组大小。Proxy对象能帮你更好地控制软件的在某些方面的行为,否则你就不能控制这些行为,比如在上面的情况里,这种行为是指隐式类型转换,所以它值得你去学习和使用。你可能会问你如何去学习它呢?一种方法是转向条款M30;它专门讨论proxy classes。

    在你跳到条款M30之前,再仔细考虑一下本条款的内容。让编译器进行隐式类型转换所造成的弊端要大于它所带来的好处,所以除非你确实需要,不要定义类型转换函数。

    展开全文
  • 缺省参数、函数重载

    2018-10-31 12:53:52
    用法:缺省参数是声明或定义函数时为函数的参数指定一些默认值。调用,若没有给实参,就用默认值,若给定实参,就用实参的值。 缺省参数又分为全缺省参数和半缺省参数: 当声明和定义分离,只能声明中...

    缺省参数:

    用法:缺省参数是声明或定义函数时为函数的参数指定一些默认值。在调用时,若没有给实参,就用默认值,若给定实参,就用实参的值。

    缺省参数又分为全缺省参数和半缺省参数:

    当声明和定义分离时,只能在声明中出现缺省参数。 当声明和定义一起出现时,就不存在在哪个中出现了。。。

    • 全缺省参数:

    • 半缺省参数:(切记使用半缺省参数时缺省值必须从最右边开始,且不能间隔给出)

     

     

    函数重载:

         函数重载的目的是为了解决功能相同但数据类型不同的函数。但是这些函数的参数列表(参数个数,类型,顺序)必须不同。

    • 参数类型不同 

    那么在C++中的函数重载的原理是什么呢?

    •  首先我们要知道一个程序运行起来经过了以下四个步骤:预处理,编译,汇编,链接。

    在C++的底层,有重命名机制,其也考虑返回值的问题。

    在VS中我们可以看到,编译器在底层用的是一个更复杂的名字(包括函数的名字和参数类型)。所以只要参数列表不同,编译器在编译时对函数名字进行重新修饰,实现函数重载。

     

    注意:函数重载必须是参数列表的不同,与其返回值无关。

     

     

     

    展开全文
  • C++入门一、命名空间1.1 命名空间的定义1.1.1 普通的命名空间1.2.2 命名空间的嵌套1.3.1 同一个...输出三、缺省参数3.1 缺省参数的定义3.2 缺省参数分类3.2.1 全缺省参数3.2.2 半缺省参数四、函数重载4.1 函数重载概念4

    我们知道C语言的hello world文件后缀为’.c’,那么C++的文件后缀为’.cpp’
    首先写出第一个C++程序Hello World来了解C++这门语言。

    #include <iostream>
    using namespace std;	//1.命名空间
    
    int main()
    {
      cout<<"Hello World!"<<endl;	//2.输出
      return 0;
    }
    
    

    从C++的hello world可以看出与C语言的直观区别

    • C语言中的头文件以’.h’结尾,而C++却是直接书写头文件名
    • C++中还多可一条语句using namespace std
    • C++中的输出语句为cout,换行语句为endl

    那么接下来就从下面几个方面展开叙述C++的基础知识:

    一、命名空间

    在C/C++中,变量、函数和类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。

    举个栗子,解释命名冲突:

    假设你和你的同事分别负责一个项目的不同模块,如果你定义了变量a你的同事也定义了变量a,那么程序在使用变量a时使用的是谁定义的变量呢?在C++中使用命名空间namespace来解决这个问题

    1.1 命名空间的定义

    定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。定义命名空间有下面三种方式:

    1.1.1 普通的命名空间

    命名空间中的内容既可以是变量定义,也可以是函数定义

    //1. 普通的命名空间
    namespace N1  // N1为命名空间的名称
    {
       // 命名空间中的内容,既可以定义变量,也可以定义函数
       int a;
       int Add(int left, int right)
       {
           return left + right;
       }
    }
    

    1.2.2 命名空间的嵌套

    可以N3嵌套在N2中

    //2. 命名空间可以嵌套
    namespace N2
    {
       int a;
       int b;
       int Add(int left, int right)
       {
           return left + right;
       }
        
       namespace N3	//N3嵌套在N2中
       {
           int c;
           int d;
           int Sub(int left, int right)
           {
               return left - right;
           }
       }
    }
    

    1.3.1 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。

    下面N1的内容会和前面1中N1的内容合并到一个空间中

    namespace N1
    {
       int Mul(int left, int right)
       {
           return left * right;
       }
    }
    

    注意:
    一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中,也就是只能在自己的命名空间中有效。

    1.2 命令空间的使用

    #include<stdio.h>
    
    namespace N2
    {
      int a = 10;
      int b = 20;
      int Add(int x, int y)
      {
        return x + y;
      }
      namespace N3
      {
        int c = 30;
        int d = 40;
        int Sub(int x ,int y)
        {
          return x - y;
        }
      }
    }
    
    int main()
    {
      printf("%d\n", a);  // 该语句编译出错,无法识别a
      printf("%d\n", c);  // 该语句编译出错,无法识别c
    
      return 0;
    }
    
    

    上述程序如果直接使用a,b变量则会报错,我们以第二种为例来讲解命名空间使用的三种方式:

    1.2.1 加命名空间名称及作用域限定符

    在变量名前加命名空间名称N1和作用域限定符::就可以访问变量a和c了

    int main()
    {
      printf("%d\n", N2::a);
      printf("%d\n", N2::N3::c);
    
      return 0;
    }
    
    

    结果:
    在这里插入图片描述

    1.2.2 使用using将命名空间中成员引入

    using N2::a;
    
    int main()
    {
      printf("%d\n", a);
      printf("%d\n", N2::N3::c);
    
      return 0;
    }
    

    在这里插入图片描述

    访问变量a就以直接使用而不加N2::了

    1.2.3 使用using namespace 命名空间名称引入

    using namespace N2;
    
    int main()
    {
      printf("%d\n", a);
      printf("%d\n", N3::c);
      printf("%d\n", Add(20,30));
    
      return 0;
    }
    
    

    在这里插入图片描述

    访问N2变量中变量与函数以及嵌套的命名空间可以不用输入N2::

    二、输入&输出

    1. 使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含< iostream >头文件以及std标准命名空间。
    2. 使用C++输入输出更方便,不需增加数据格式控制,比如:整形–%d,字符–%c等
    #include <iostream>
    using namespace std;
     
    int main()
    {
        int a;
        double b;
        char c;
        
        cin>>a;
        cin>>b>>c;
        
        cout<<a<<endl;
        cout<<b<<"  "<<c<<endl;
        return 0;
    }
    

    在这里插入图片描述

    三、缺省参数

    3.1 缺省参数的定义

    缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。

    #include <iostream>
    
    using namespace std;
    
    void TestFun(int a = 10)
    {
      cout<<a<<endl;
    }
    
    int main()
    {
      TestFun();	// 没有传参时,使用参数的默认值
      TestFun(20);  // 传参时,使用指定的实参
    }
    
    

    在这里插入图片描述

    3.2 缺省参数分类

    3.2.1 全缺省参数

    所有参数全部具有默认值就是全全缺省参数

    void TestFun1(int a=10, int b =20, int c=30)
    {
      cout<<"a= "<<a<<endl;
      cout<<"b= "<<b<<endl;
      cout<<"c= "<<c<<endl;
    }
    
    int main()
    {
      TestFun1();
      TestFun1(1);
      TestFun1(1,2);
      TestFun1(1,2,3);
    
    }
    
    

    在这里插入图片描述

    3.2.2 半缺省参数

    只给部分参数的默认值就是半缺省参数

    void TestFun2(int a, int b=20, int c=30)
    {
      cout<<"a= "<<a<<endl;
      cout<<"b= "<<b<<endl;
      cout<<"c= "<<c<<endl;
      
    }
    
    

    在这里插入图片描述
    注意:
    1. 半缺省参数必须从右往左依次来给出,不能间隔着给
    2. 缺省参数不能在函数声明和定义中同时出现

    //a.h
    void TestFunc(int a = 10);
     
    // a.c
    void TestFunc(int a = 20)
    {}
     
    // 注意:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那
    个缺省值。
    

    3. 缺省值必须是常量或者全局变量
    4. C语言不支持(编译器不支持)

    四、函数重载

    4.1 函数重载概念

    函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或类型或顺序)必须不同,常用来处理实现功能类似数据类型不同的问题。

    #include <iostream>
    using namespace std;
    
    int Add(int left ,int right)
    {
      return left+right; 
    }
     
    double Add(double left, double right)
    {
      return left+right;
    }
     
    long Add(long left, long right)
    {
      return left+right;
    
    }
     
    int main()
    {
      Add(10, 20);
      Add(10.0, 20.0);
      Add(10L, 20L);
    
      return 0;
    
    }
    

    4.2 C++支持函数重载,而C语言不支持函数重载的原因

    4.2.1 过程分析

    在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
    这四个阶段具体的功能可以参考这篇博客第二节
    《Linux的C语言开发工具——通过进度条小程序学习使用gcc、gdb和make/Makefile工具》
    假设我们有以下程序

    #include <iostream>
    using namespace std;
    
    int Add(int left ,int right)
    {
      return left+right; 
    }
     
    double Add(double left, double right)
    {
      return left+right;
    }
     
    long Add(long left, long right)
    {
      return left+right;
    
    }
     
    int main()
    {
      Add(10, 20);
      Add(10.0, 20.0);
      Add(10L, 20L);
    
      return 0;
    
    }
    
    

    如果使用C语言编译会发现由于函数名相同,编译不通过,而C++却可以编译通过
    在这里插入图片描述
    我们反汇编查看C和C++中对于函数名的修饰是怎样的?

    • 在Linux下使用以下命名就可以查看了C语言反汇编结果
      objdump -S 05-C_FunOverload

    在这里插入图片描述
    结论:在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变。

    • 在Linux下使用以下命名就可以查看了C++反汇编结果
      objdump -S 05-FunOverload
      在这里插入图片描述
      结论:在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中。

    4.2.2 结论

    通过分析我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰后变成【_Z+函数长度+函数名+类型首字母】。

    通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。

    4.3 extern “C”

    有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译。比如:tcmalloc是google用C++实现的一个项目,他提供tcmallc()和tcfree两个接口来使用,但如果是C项目就没办法使用,那么他就使用extern “C”来解决。

    extern "C" int Add(int left, int right);
     
    int main()
    {
        Add(1,2);
        return 0;
    }
    

    五、内联函数

    5.1 概念

    以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。
    在这里插入图片描述
    在这里插入图片描述

    如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用

    5.2 特性

    1. inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。
    2. inline对于编译器而言只是一个建议,编译器会自动优化,这里需要注意并不是添加了inline关键字,编译器就会无脑将函数变为内联,仅仅是建议,例如:如果定义为inline的函数体内有循环/递归等,编译器优化时会忽略掉内联。
    3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。

    Q1:宏的优缺点?

    优点:

    1.增强代码的复用性。
    2.提高性能。

    缺点:

    1.不方便调试宏。(因为预编译阶段进行了替换)
    2.导致代码可读性差,可维护性差,容易误用。
    3.没有类型安全的检查 。

    Q2:C++有哪些技术替代宏?

    1. 对于常量的定义,可以换用const关键字来替代宏
    2. 对于函数的定义,可以换用inline关键字替代宏
    展开全文
  • 关于C++缺省构造函数的讲解——精解

    千次阅读 多人点赞 2019-03-15 12:36:00
    构造函数 类的构造函数是类的一种特殊的成员函数,它会每次创建类的新对象执行, 构造函数的名称与类的名称是完全相同的,...若程序员没有主动给类定义构造函数,编译器自动给一个缺省的构造函数。 一旦程序员定...
  • 直接定义这样的函数时,Python会报错: [python] view plain copy print? Python 3.4.2 (v3.4.2:ab2c023a9432, Oct 6 2014, 22:16:31) [MSC v.1600 64 bit (AMD64)] on win32 Type...
  • 一、内联函数背景:函数调用是有时间...编译器处理对内联函数的调用语句,是将整个函数的代码插入到调用语句处,而不会产生调用函数的语句。 代码实例:(inline代表内联函数)inline int Max(int a,int b) {...
  • C++基础:缺省构造函数

    万次阅读 多人点赞 2014-05-10 16:01:50
    缺省构造函数是C++以及其他的一些面向对象的程序设计语言中,
  • 本文学习Python函数,包括:函数调用 定义函数 函数的参数 递归函数我们知道圆的面积计算公式为:S = π r*r当我们知道半径r的值,就可以根据公式计算出面积。假设我们需要计算3个不同大小的圆的面积:r1 = 12.34 ...
  • 函数重载:是函数的一种特殊情况,C++允许同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据不同的问题 函数参数个数不同 函数...
  • 构造函数的参数缺省

    千次阅读 2013-09-20 09:06:20
    构造函数中参数的值既可以通过实参传递,也可以指定为某些默认值,即如果用户不指定实参值,编译系统就使形参取默认值。构造函数中也可以采用这样的方法来实现初始... //声明构造函数时指定默认参数  int volume
  • 继承中虚函数缺省参数值问题

    千次阅读 2015-05-20 16:40:05
    如果类继承中重新定义了虚函数,那么虚函数中的缺省参数不要重新定义。 用一句话来解释原因就是:虚函数是动态绑定的(dynamically bound),但是缺省参数却是静态绑定的(statically bound)。 静态类型和动态类型 首先...
  • (1)同一个C++程序中出现多个同名函数。 (2)函数重载的条件:重载函数有不同的形参类型表;重载函数有不同的参数个数。 这两个条件满足一个即可:但是在函数的调用的时候会存在隐形的变量类型的提升: 字符型->...
  • 第5章 函数重载与缺省函数 函数重载的本质就是允许函数同名 函数重载和缺省参数都为调用函数提供了方便。
  • 定义使用函数指针

    千次阅读 2013-09-06 09:29:17
    定义一个函数指针 且不论语法,有两种不同形式的指针函数: 一个是指向普通的C函数的指针和C++的静态成员函数,另外一个是指向C++的非静态成员函数的指针。这两者的基本区别是所有指向非静态成员函数的指针都 ...
  • 而函数的声明的作用则是把函数的名字,函数类型以及形参的类型、个数和顺序通知编译系统,以便调用该函数进行对照检查(例如,函数名是否正确,实参与形参的类型和个数是否一致),它不包括函数体。 ————...
  • C++缺省参数和函数重载

    千次阅读 2015-10-04 20:02:23
    C++中参数可以设置缺省值,设置了缺省值之后,这个参数调用时可以省略。 注意:设置缺省值的参数只能是最后的几个参数。也就是说某一个参数一旦设置了缺省值,其后而的参数也必须设置缺省值。例如:f()函数有...
  • C++重载函数缺省参数

    千次阅读 2010-04-09 23:00:00
    1,定义函数重载的必要性?a,比如打印3个不同类型的数据:不得不用三个不同的函数,如print_int(),print_char(),print_float(),增加编程工作量。b,构造函数名字预先由类的名字确定,所以只能有一个构造函数名。不...
  • 函数定义以及参数

    万次阅读 2018-01-02 20:33:15
    可以通过函数名在程序的不同地方多次执行。(函数调用) 函数分为预定义函数和自定义函数。 预定义函数可以直接使用。 自定义函数需要,是用户自己编写。 2.为什么要使用函数? (1)降低编程的难度。 ...
  • 变量 变量和数据都是保存再内存中的 python中函数的参数...局部变量是函数内部定义的变量,只能在函数内部使用 函数执行结束后,局部变量会被系统回收 全局变量是整个py文件中声明 注:函数在执行,变量...
  • 简要:对于一个空类,C++编译器默认生成四个成员函数:默认... 默认构造函数(default constructor)就是没有显式提供初始化式调用的构造函数。它由不带参数的构造函数,或者为所有的形参提供默认实参的构造函...
  • 2.3深入探讨函数: 2.3.1构造函数、默认构造函数缺省构造函数 对于上面的实例,它已经能完成绝大部分工作了,但它还是不完善的,还有许许多多的细节等到我们去完善!也许有的同学已经注意到了,当我创建完...
  • c++声明函数原型的可为一个或者多个参数指定默认(缺省)的参数值,当函数调用的时候如果没有传递该参数值,编译器会自动用默认值代替。 //函数的默认参数 指定x的默认值为10 y为20 int my_add(int x=10,int y=...
  • C++中参数可以设置缺省值,设置了缺省值之后,这个参数调用时可以省略。 注意:设置缺省值的参数只能是最后的几个参数。也就是说某一个参数一旦设置了缺省值,其后而的参数也必须设置缺省值。例如:f()函数有...
  • java缺省构造函数(编译器)

    千次阅读 2006-01-04 22:11:00
    Java编译器会定义中加入一个构造函数(缺省构造函数,default constructor),这个构造函数的名字和类名相同,但是没有参数,而且这个构造函数没有任何指令。这种构造方法又叫做空构造方法。因为它不做任何事情。
  • 函数重载概念面试题:为什么函数重载C++可以,C语言不可以?(详解) 1.关键字 C++总计63个关键字 2.命名空间 C/C++中,变量、函数和类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会...
  • Python3函数传参和函数形参定义方式

    千次阅读 2018-01-20 11:00:10
    1、函数调用的参数传递方式:(调用函数)  传递方式有以下四种:    位置传参    序列传参    关键字传参  字典关键字传参 (1)位置传参:  实际参数(实参)的对应关系与形式参数(形参)对应...
  • [c/c++]函数参数缺省

    万次阅读 2007-10-29 15:15:00
    参数缺省值只能出现在函数的声明中,而不能出现在定义体中。 例如: void Foo(int x=0, int y=0); // 正确,缺省值出现在函数的声明中 void Foo(int x=0, int y=0) // 错误,缺省值出现在函数定义体中 { … } 为...
  • -------------------------------------------------------------------------------- 条款24: 在函数重载和设定参数缺省值间慎重选择会对函数重载和设定参数缺省值产生混淆的原因在于,它们都允许一个函数以多种...
  • 简述 C/C++ 的函数名修饰规则以及函数调用约定

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 131,003
精华内容 52,401
关键字:

在定义函数时函数名可以缺省