• C++第八章__内联函数__引用变量_将引用变量作为函数参数__使用引用作为形参,会改变对应实参的值(左值的概念)__将引用应用于结构__为何要使用引用__将引用用于类和对象_对象、继承和引用__默认参数__函数重载__重载示例__函数模板__重载的模板__显式具体化__实例化和具体化(包含的知识点为结构数组,指针数组,模板等)__自己选择使用哪个函数模板__模板函数的优化,设计到相关函数的引进


    目录:

    内联函数

    /*
    01)c语言中的宏,例如:
      #define SQUARE(X) X*X

      a = SQUARE(5.0); //等价于a = 5.0*5.0 正常
      b = SQUARE(4.5+7.5) //等价于b = 4.5+7.5*4.5+7.5,则不

           正常了,可以通过#define SQUARE(X) ((X)*(X))方法改进 

      d = SQUARE(c++) //等价于d = c++ * c++ c最后的值会增加两次 假如c等于3 c++ * c++就等价于3*4 最后c等于5,

          而在宏定义出现类似的错误的时候,可以考虑使用内联函数                                                            

    02)要是有内联函数,要采取下述措施之一:
      在函数生命前加上关键字inline
      在函数定义前加上关键字inline
      通常的做法是省略原型(函数声明),将整个定义放在本应
    提供原型(函数声明)的地方                                                                                                                   

    03)内敛函数可以提高程序的执行速度,代价是 程序占用的

         内存增加,假如程序要在10个不同的地方调用同一个内联 函数,则该程序将包含该函数代码的十个副本                  

    04)以下代码中也包含了c++后缀的使用技巧,需要注意的一个点
    */

    附图说明普通函数调用和内联函数调用的区别

     1 #include <iostream>
     2 
     3 using namespace std;
     4 
     5 //内联函数的定义
     6 inline double square(double x) { return x * x; }  //将整个函数定义放在了一行,如果函数定义占用多行,那么使用内联函数就不太合适
     7 
     8 int main()
     9 {
    10     double a, b;
    11     double c = 13.0;
    12 
    13     a = square(5.0);  //内联函数的调用
    14     b= square(4.5+7.5);  //内联函数的调用
    15     cout << "a =  " << a << endl;
    16     cout << "a =  " << b << endl;
    17 
    18     cout << "c = " << c;
    19     double d = square(c++);  //后缀运算符是先使用后修改,所以c先将13传入内联函数,后将自身值递增为14
    20     cout << "  c square = " << d << endl;
    21 
    22     cout << "Now c = " << c << endl;
    23 
    24     system("pause");
    25     return 0;
    26 }
    内联函数的定义和调用

    执行结果:

    引用变量

    /*
    01)c和c++使用&来指示变量的地址。c++赋予了&另一个含义,将其用来声明印用
      int rats;
      int & rodents = rats; //将rodents作为rats的别名 int &表示是指向int的引用,其中&不是地址运算符
      上述声明允许将rats和rodents呼唤,他们指向相同的内存单元,将rodets加1将会影响两个变量
      更准确的说rodents++操作将一个有两个名称的变量加1
    02)必须在声明引用时将其初始化 如:
      int rats;
      int & rodents;
      rodents = rats; //这样做是错误的
    03)引用和指针是有区别的
      int rets;
      int* parts = &rats; //声明并初始化一个指针parts,该指针指向rats
      int & rodents = rats; //声明并初始化一个引用rodents,使rodents成为rats的别名
      这样表达式rodents和*parts都可以和rats呼唤,而表达式&rodents和parts也都可以和&rats互换
    04)引用更接近于const指针,必须在创建时进行初始化,一旦与某个变量关联起来,就一直效忠于它
      也就是说:int & rodents = rats;
      实际上是该代码的伪装表示:int* const pr = &rats;
      其中引用扮演的角色与表达式*pr相同
    05)int rats = 101;
      int* pt = &rats; //声明并初始化一个指针parts,该指针指向rats
      int & rodents = *pt; //由于*pt就等价于rats,所以该句的意思就是声明并初始化一个引用rodents,使rodents成为rats的别名
      int business = 50;
      pt = &business; //pt改为指向business,但是rodents还是指向rats
    */

     1 #include <iostream>
     2 
     3 using namespace std;  
     4 
     5 int main()
     6 {
     7     int rats = 101;
     8     int & rodents = rats;  //声明并初始化一个引用rodents,使rodents成为rats的别名
     9 
    10     cout << "value of rats is: " << rats << "; " << "Address of rats is: " << &rats << endl;
    11     cout << "value of rodents is: " << rodents << "; " << "Address of rodents is: " << &rodents << endl;
    12 
    13     int business = 50;
    14     rodents = business;  //等价于rats = business,执行完此句之后,rats和rodents的值都会改变为50,且二者的地址相同,但二者的地址和business是不一样的
    15     cout << "value of business is: " << business << "; " << "Address of business is: " << &business << endl;
    16     cout << "value of rats is: " << rats << "; " << "Address of rats is: " << &rats << endl;
    17     cout << "value of rodents is: " << rodents << "; " << "Address of rodents is: " << &rodents << endl;
    18 
    19     system("pause");
    20     return 0;
    21 }
    引用变量的声明和初始化

    执行结果为:

     将引用变量作为函数参数

    按值传递和按引用传递的区别如下图所示:

    由于简单,直接上代码了吧那就:

     1 //使用引用变量、指针、和普通变量作为函数参数交换形参的值
     2 #include <iostream>
     3 
     4 void swapr(int & a, int & b);  //声明参数为引用变量的函数
     5 void swapp(int* a, int* b);  //声明参数为指针的函数
     6 void swapv(int a, int b);  //声明参数为普通变量的函数
     7 
     8 int main()
     9 {
    10     using namespace std;
    11 
    12     int wallet1 = 200;
    13     int wallet2 = 300;
    14 
    15     cout << "原始数据为:wallet1 = " << wallet1 << endl;
    16     cout << "原始数据为:wallet2 = " << wallet2 << endl;
    17 
    18     swapr(wallet1, wallet2);  //调用参数为引用变量的函数
    19     cout << "调用参数为引用变量的函数:wallet1 = " << wallet1 << endl;
    20     cout << "调用参数为引用变量的函数:wallet2 = " << wallet2 << endl;
    21 
    22     swapp(&wallet1, &wallet2);  //调用参数为指针的函数
    23     cout << "调用参数为指针的函数:wallet1 = " << wallet1 << endl;
    24     cout << "调用参数为指针的函数:wallet2 = " << wallet2 << endl;
    25 
    26     swapv(wallet1, wallet2);  //调用参数为普通变量的函数
    27     cout << "调用参数为普通变量的函数:wallet1 = " << wallet1 << endl;
    28     cout << "调用参数为普通变量的函数:wallet2 = " << wallet2 << endl;
    29 
    30     system("pause");
    31     return 0;
    32 }
    33 
    34 void swapr(int & a, int & b)  //该子函数修改的是主函数中的值,假如在主函数中调用该子函数时候传入了主函数中的值
    35 {
    36     int temp = a;
    37     a = b;
    38     b = temp;
    39 }
    40 void swapp(int* a, int* b)  ////该子函数修改的是主函数中的值,假如在主函数中调用该子函数时候传入了主函数中的值
    41 {
    42     int temp = *a;
    43     *a = *b;
    44     *b = temp;
    45 }
    46 void swapv(int a, int b)  //该子函数只是复制了传入该子函数的值给a、b,并交换了a和b的值,并没有交换主函数中的wallet1和wallet2的值
    47 {
    48     int temp = a;
    49     a = b;
    50     b = temp;
    51 }
    使用引用变量、指针、和普通变量作为函数参数交换形参的值

    执行结果为:

    使用引用作为形参,会改变主函数中的实参的值 

    /*
    01)左值:左值参数是可被引用的数据对象,例如:变量、数组元素、结构成员、引用和接触引用的指针都是左值
         非左值:包括字面常量(用引号括起的字符串除外,它们由地址表示)和含有多项式的表达式
    02)应尽可能在声明函数中的形参时候使用const,原因如下
         I 使用const可以避免无意中修改数据
         II 使用const能使函数能够处理const和非const实参,否则子函数就只能接收非const实参(假如子函数形参类型不是const)
    */

     1 #include <iostream>
     2 
     3 double cube(double a);  //声明一个普通函数
     4 double refcube(double &ra);  //声明一个形参为引用的函数
     5 
     6 int main()
     7 {
     8     using namespace std;
     9     
    10     double x = 3.0;  //定义常规变量
    11 
    12     cout << cube(x) << " = cube of " << x << endl;  //调用普通函数,此时x的值不会改变
    13 
    14     cout << refcube(x) << " = cube of " << x << endl;  //但是这样将refcube(x)和x一起打印的话还是x的值是不变的,执行完这一句之后x = ra的值即27
    15 
    16     double z = refcube(x);  //调用引用函数,此时x的值会随着子函数中ra的值的改变而改变 此时x=27
    17     cout <<z<< " = cube of " << x << endl;  //打印19683= cube of 19683
    18 
    19     /*如果ra是一个变量的别名(即引用),则实参应该是变量,而不应该是表达式*/
    20     //double z1 = refcube(x+10);  //不合法
    21     //double z1 = refcube(10);  //不合法
    22     double yo[3] = { 2.2,3.3,4.5 };  //声明一个数组
    23 
    24     double z1 = refcube(yo[2]);  //合法  数组元素
    25     cout << "refcube(yo[2]) = " << z1 << endl;
    26 
    27     /* 有关左值的概念 */
    28     double side = 3.0;
    29     double* pd = &side;  //创建指向side的指针pd
    30     double & rd = side;  //创建引用变量,rd即等价于side
    31     long edge = 5L;  //创建长型变量,注意要在数字最后加上字母L
    32     double lens[4] = { 2.0,5.0,10.0,12.0 };  //创建数组
    33 
    34     double c1 = refcube(side);  //ra is side
    35     double c2 = refcube(lens[2]);  //ra is lens[2]
    36     double c3 = refcube(rd);  //ra is rd is side
    37     double c4 = refcube(*pd); //ra is *pd is side
    38 
    39     double c5 = refcube(edge);  //不合法,因为ra是double类型的,而edge是long类型的
    40     double c6 = refcube(7.0); //7.0是非左值,不合法
    41     double c7 = refcube(side + 7.0);  //side+7.0是包含多项式的表达式,是非左值,不合法
    42     //c++遇到上述三种类型,就会创建类型正确的匿名变量,将函数调用的参数的值传递给改匿名变量,并让参数来引用改变量
    43 
    44     
    45 
    46     system("pause");
    47     return 0;
    48 }
    49 
    50 double cube(double a)
    51 {
    52     /*a = a * a*a;*/
    53     a = a*a * a;
    54 
    55     return a;
    56 }
    57 double refcube(double &ra)
    58 {
    59     //ra = ra * ra*ra;
    60     ra = ra* ra * ra;  //也可以这么写  ra *= ra*ra;
    61 
    62     return ra;
    63 }
    使用引用作为形参,会改变主函数中的实参的值

     执行结果为:

     将引用应用于结构 

    /*
    01) 使用结构引用参数的方式于使用基本便令引用方式相同,只需在结构参数使用引用运算符&即可
    02) 假如有如下结构:
      struct free_throws
      {
        std::string name;
        string made;
        int attemps;
        float percent;
      };
      则可以这样声明子函数,在子函数中将指向改结构的引用,作为参数
      void set_pc(free_throws & ft); //可以更改结构中的参数
      void display(const free_throws & ft); //加上const则不能更改结构中的参数
    */

     1 #include <iostream>
     2 #include <string>
     3 
     4 
     5 struct free_throws
     6 {
     7     std::string name;  //新建一个string型字符串变量name
     8     int  made;
     9     int attemps;
    10     float percent;
    11 };
    12 
    13 void display(const free_throws & ft);  //声明一个函数,形参为指向结构free_throws的引用ft,但是不可以通过ft来改变结构中的值,此处的ft为const引用参数
    14 void set_pc(free_throws & ft); //声明一个函数,形参为指向结构free_throws的引用ft
    15 free_throws & accumulate(free_throws & target, const free_throws & source);  //声明一个返回值为free_throws结构引用的函数accumulate,形参为两个指向结构的引用
    16 
    17 int main()
    18 {
    19     //初始化结构变量
    20     free_throws one = { "Ifelsa Branch",13,14 }; //没有被赋值的就默认为0,比如以下均没有对percent赋值,则percent均为0
    21     free_throws two = { "Andor Knott",10,16 };
    22     free_throws three = { "Mininie Max",7,9 };
    23     free_throws four = { "Whily Looper",5,9 };
    24     free_throws five = { "Long Long ago",6,14 };
    25     free_throws team = { "Throwgoods",0,0 };
    26     free_throws dup;  //定义结构dup,没有初始化
    27 
    28     //01简单调用形参结构的子函数
    29     set_pc(one);  //调用子函数,参数为指向结构的引用  此句为填充one结构中percent的值
    30     display(one);  //调用子函数,参数为指向结构的引用  此句为显示整个one结构中的值
    31 
    32 
    33     //02调用返回值为结构的子函数,但是这个返回值并没有使用,而是用子函数中的引用去修改主函数中结构的值
    34     accumulate(team, one);  //这一句的确是会返回一个结构,但是由于该函数的形参为引用,所以传入的team结构中的值也会被改变
    35     //其中在accumulate子函数中,第二个引用为conts类型,对应one,只是 用以下one结构中的值,不会改变one中的值,如果在子函数中要求改变,会报错
    36     //在accumulate子函数中,第二个参数为非const类型,故可以在子函数中更改team中的值,且在子函数中修改target的值就是修改team的值
    37     display(team);
    38 
    39     //03使用accumulate子函数的返回值,作为另外一子函数display的实参,accumulate子函数的返回值为引用,进一步说是结构引用
    40     display(accumulate(team, two));    //使用accumulate(team, two)的返回值(team)作为display的实参
    41     accumulate(accumulate(team, three),four);
    42     display(team);
    43 
    44     //04将返回值赋给一个没有初始化的结构 dup
    45     dup = accumulate(team, five);
    46     std::cout << "display(team): 
    ";
    47     display(team);
    48 
    49     std::cout <<"Displaying dup after assignment: '
    '";
    50     display(dup);
    51 
    52     set_pc(four);  //重新设置结构four中的percent的值
    53 
    54     system("pause");
    55     return 0;
    56 }
    57 
    58 void display(const free_throws & ft)
    59 {
    60     using std::cout;
    61     cout << "Name: " << ft.name << '
    ';  //其中'
    '表示换行,用endl也是可以的
    62     cout << " Made: " << ft.made << '	';
    63     cout << "Attemps: " << ft.attemps << '	';  //'	'表示制表符
    64     cout << "Percent: " << ft.percent << '
    ';
    65 
    66 }
    67 void set_pc(free_throws & ft)  //由于没有对ft加限定符const,所以该子函数允许修改原结构中的变量的值
    68 {
    69     if (ft.attemps != 0)
    70         ft.percent = 100.0f*float(ft.made) / float(ft.attemps);  //100.0f表示100为float型
    71     else
    72         ft.percent = 0;  //percent的值就被修改,而不用返回percent的值到主函数,是直接修改的主函数中的结构原型中的结构参数,而不是修改的副本
    73 }
    74 free_throws & accumulate(free_throws & target, const free_throws & source)
    75 {
    76     target.attemps += source.attemps;  //赋值操作
    77     target.made += source.made;  //赋值操作
    78     set_pc(target);  //在子函数中调用子函数,重新对指向结构的引用target赋值
    79     return target;  //返回值为结构引用 或者说返回值为target,假如传入的是team,那么target就是team的引用,即target和team是等价的,返回target就是返回team了
    80 }
    将引用应用于结构

    执行结果为:

    /*
    01)对于srt_pc(one); 必须使用按引用传递参数,不可使用按值传递,如果使用按值传递,则修改不了结构one中的percent的值
    02)另一种方法是使用指针参数并传递地址 *****
      set_pc(&one)
      ......
      void set_pc(free_throws* pt)
      {
        if (pt->attemps != 0)
          pt->percent = 100.0f*float(pt->made) / float(pt->attemps); //100.0f表示100为float型
        else
          pt->percent = 0;
      }
      //注意:由于ft是指向结构的指针,所以只可以使用间接成员运算符->来访问结构中的成员
    03)display(one); 由于只是显示结构中的内容,所以使用了一个const引用参数

     为何要使用引用

    /*
    01)传统返回机制于安宅传递函数参数类似,计算关键字return后的表达式,并将结构返回给调用函数
     从概念上说这个只被复制到一个临时位置,而调用程序将使用这个值
    02)dup = acuumulate(team,five); //注意返回值就是team,因为传入的时候target是team的引用,那么target和team是等价的
     如果accumulate返回的是一个结构,而不是指向结构的引用,将把整个结构复制到一个临时位置,再将
     这个拷贝复制给dup。但在返回值为引用时,将直接把team复制到dup,效率会更高。
    */

    将引用用于类和对象

     1 #include <iostream>
     2 #include <string>
     3 
     4 using namespace std;
     5 
     6 string version1(const string & s1, const string & s2);  //声明一个返回值为string类型字符串、形参为两个指向string类型变量的引用
     7 const string & version2(string & s1, const string & s2);  //声明一个返回值为指向const string类型变量的引用、形参也为两个引用的子函数
     8 const string & version3(string & s1, const string & s2);
     9 
    10 int main()
    11 {
    12     string input;  //定义一个string类变量input
    13     string copy;   //定义一个string类变量input
    14     string result;  //定义一个string类变量input
    15 
    16     cout << "请输入一个字符串或句子:";
    17     getline(cin, input);  //此句是对于string字符串接收,而cin.getline(charr,20);中charr是一个char数组用于c风格
    18     copy = input;  //复制数据给copy
    19     cout << "您的输入为:" << input << endl;
    20     result = version1(input, "***");
    21     cout << "调用vision2后的结果字符串result为:" << result << endl;
    22     cout << "调用vision2后的初始字符串input为:" << input << endl;
    23 
    24     result = version2(input, "###");  //与version2(input,"###"); result = input;是等价的,因为s1等价于input改变s1就是改变temp的值
    25     /*
    26        对于 version2(input, "###");中的input和"###"
    27        01)input是string类型的变量,传递到version2中对应string & s1,即s1是指向input的引用,合理
    28        02)"###"是一个char类型的字符串(实际是一个指针,类型为const char*),那么将一个const char*赋给一个以用是否合理呢
    29           这里需要说明的是:在c++中string定义了char*向string转换功能,这使得可以使用c等个字符串来初始化string对象,
    30           在将"###"传递给string & s2时,c++就会将char*或者是const char*转换为string类,再赋值给string & s2,这样就合理了
    31      03)将int型实参传递给const double & ra 也是先将int型实参转换为double型,再赋值给ra的
    32      04)此时input的内容将会被改变!!!
    33      */
    34     cout << "调用vision2后的结果字符串result为:" << result << endl;
    35     cout << "调用vision2后的初始字符串input为:" << input << endl;
    36 
    37     //result = version3(input, "@@@");  //会报错,因为在version3中返回值temp为临时变量,在执行完该子函数后,temp就会消失
    38                                      //而此时要将一个消失了的变量赋值给result,故编译器会报错
    39 
    40     system("pause");
    41     return 0;
    42 }
    43 
    44 string version1(const string & s1, const string & s2)
    45 {
    46     string temp;
    47     temp = s2 + s1 + s2;  //将传入的字符串合并在一起
    48     return temp;  //返回刚刚合并的字符串
    49 }
    50 const string & version2(string & s1, const string & s2)
    51 {
    52     s1 = s2 + s1 + s2;
    53     return s1;
    54 }
    55 
    56 const string & version3(string & s1, const string & s2)
    57 {
    58     string temp;
    59 
    60     temp = s2 + s1 + s2;
    61 
    62     return temp;  //如果返回值不是引用,是普通的变量,在使用result = version3(input, "@@@");时不会出错
    63                  //函数试图引用已释放的内存
    64 }
    通过使用引用,让函数将类string、ostream、istream、ofstream、ifstream等类的对象做为参数

    注意:在返回值为引用的时候,不可以将在子函数内部定义的一个临时变量返回,否则在主函数中调用时候,会报错

              不调用只是会发出警告,详见上面的代码中的version3子函数

    执行结果为:

     对象、继承和引用

    /*
    01)将特性从一个类传递给另一个类的语言特性被称为继承,比如ofstream对象可以使用ostream类的方法
     这使得文件输入/输出格式与控制台(屏幕输出)输入输出相同
    02)那么ostream是基类(因为ofstream是建立在它的基础之上的),而ofstream是派生类(因为它是从ostream派生而来的)
     派生类继承了基类的方法,这意味着ofstream对象可以使用基类的特性,如格式化方法precision()和setf()
    03)继承的另一个特征是,基类引用可以指向派生类对象,而无需进行强制转换
     这种特征的结果是,可以定义一个接收基类引用作为参数的函数,调用该函数时,可以将基类对象作为参数
     也可以将派生类对象作为参数。如参数类型为ostream &的函数可以接收ostream对象(如cout)或
     自己声明的ofstream对象作为参数
    */

    使用一个函数实现打印到屏幕和写入到文件(函数参数不用,使用ostream对象cout和类fstream对象fout),由于fstream是派生类,ostream是基类,派生类可以使用基类的方法,比如格式化方法precision()和setf()方法

     1 #include <iostream>
     2 #include <fstream>  //for ofstream(写文本) 和ifstream(读文本) 的使用
     3 #include <cstdlib>  //for exit()
     4 
     5 using namespace std;
     6 
     7 void file_it(ostream & os, double fo, const double fe[], int n);//声明一个形参为指向ostream类对象的引用os的函数
     8 
     9 const int LIMIT = 5;  //声明一个常量
    10 
    11 int main()
    12 {
    13     ofstream fout;  //声明一个类fstream的对象fout
    14     const char* fn = "eo-data.txt";  //声明一个指向eo-data.txt字符串的指针fn
    15     fout.open(fn);  //将fout和文件eo-data.txt关联起来
    16 
    17     if (!fout.is_open())  //判断文件是否打开失败
    18     {
    19         cout << "Can't open " << fn << ". bye.
    ";
    20         exit(EXIT_FAILURE);  //退出程序
    21     }
    22     double objective;
    23     cout << "Enter the focal lengths of your telescope objective in mm:";
    24     cin >> objective;  //输入显微镜的焦距(focal)
    25     double eps[LIMIT];  //新建一个数组,用来盛放输入的数据
    26     cout << "Enter the focal lengths,in mm,of " << LIMIT << " eyepieces:
    ";  //eyepieces目距
    27     for (int i = 0; i < LIMIT; i++)
    28     {
    29         cout << "Eyepieces #" << i + 1 << ";";
    30         cin >> eps[i];  //接收输入的参数
    31     }
    32     file_it(fout, objective, eps, LIMIT);  //参数为fout(写入到文件中)的函数调用
    33     file_it(cout, objective, eps, LIMIT);  //参数为cout(打印到屏幕中)的函数调用
    34     cout << "Done
    ";
    35 
    36     system("pause");
    37     return 0;
    38 }
    39 
    40 //os是指向cout或fout的引用,ostream是一个类名
    41 // ostream os; //创建ostream的对象os
    42 // ostream & os = cout; //创建指向cout的引用os(os也是一个ostream对象,cout也是ostrema对象)
    43 void file_it(ostream & os, double fo, const double fe[], int n) 
    44 {
    45     ios_base::fmtflags initial;  //方法setf()返回调用它之前有效的所有格式,ios_base::fmtflags是存储这种信息所需的数据类型名称
    46     initial = os.setf(ios_base::fixed);  //保存最初的格式状态,并将这种状态赋值给initial
    47     os.precision(0);
    48     os << "Focal length of objective: " << fo << "mm
    ";
    49     os.setf(ios_base::showpoint);  //将对象(os)置于显示小数点的模式,即使小数部分为零,解析来就显示小数点后面的0,否则是不显示小数点后面的0的
    50     os.precision(1);  //方法precision()用于指定显示多数为小数(假设对象处于定点模式下)
    51     os.width(12);  //设置下一次输出操作使用的字段宽度,这种设置只在显示下一个值时有效,然后恢复到默认设置
    52     os << "f.1.eyepiece";    //默认字段宽度为0,这意味着刚好能容纳下要显示的内容
    53     os.width(15); 
    54     os << "magnification" << endl;
    55     for (int i = 0; i < n; i++)
    56     {
    57         os.width(12);
    58         os << fe[i];
    59         os.width(15);
    60         os << int(fo / fe[i] + 0.5) << endl;  //焦距除以目距就是显微镜的放大倍数
    61     }
    62     os.setf(initial);  //恢复原始的输出或者是写入设置
    63 }
    使用一个函数实现打印到屏幕和写入到文件(函数参数不用,使用ostream对象cout和类fstream对象fout)

    执行结果:

    何时使用按值传递、按指针传递和按引用传递 

    /*
    使用引用参数的原因主要有两个:
    01)程序员能够修改调用函数中的数据对象
    02)通过传递引用而不是整个数据对象,可以提高程序的运行速度
    什么时候用引用、什么时候用指针、什么时候用按值传递呢,下面是一些指导原则:
    01)对于使用传递的值而不做修改的函数(不修改主函数中的数据)
     A 如果数据对象很小,如内置数据类型或小型结构,则按值传递
     B 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针
     C 如果数据对象是较大的结构,则使用const指针或const引用,以提高程序效率,这样可以节省赋值结构所需的时间
     D 如果数据对象是类对象,则使用const指针或const引用。类设计的语义常常要求使用引用,
      传递类对象参数的标准方式就是按引用传递
    02)对于修改调用函数(主函数)中数据的函数,即修改主函数中的数据
      A 如果数据对象是内置数据类型,则使用指针。如果看到fix_it(int & x),由函数名字可知,是要修改主函数传入的值
      其中x就是内置数据
     B 如果数据对象是数组,则只能使用指针
     C 如果诗句对象是结构,则使用引用或指针
     D 如果数据对象是类对象,则使用引用
    */

    默认参数

    /*
    01)默认参数定义:指当函数调用中省略了实参时,自动使用的一个值,如void wow(int n)设置成n为默认值为1
     则函数调用wow()相当于wow(1);
    02)默认参数的作用: 提高函数调用的灵活性,例如:假设有一个名为left()的函数,它将字符串和n作为参数
     并返回该字符串的前n个字符,更准确是说是返回一个指针,该指针指向前n个字符。例如left("theiry",3)
     将返回字符串"the",现在假设第二个参数的默认值被设置为1,则left("theiry",3)仍会正常工作,只不过3
     会覆盖默认值1。而且left("theiry")仍不会出错,因为默认值为1,并且left("theiry")返回字符t。
    03)如何设置默认值:通过函数原型(函数的声明),例如left()函数的声明如下:
     char* left(const char* str, int n=1); //即表示n的默认值为1,且函数的返回值为char*即字符串的地址
    04)声明规则:要为某个函数添加默认值,那么在函数声明中右边所有的参数都提供默认值
     int harpo(int n, int m=4, int j=5); //有效,右边全是默认值形式
     int chico(inr n, int m=6,int j); //无效,因为m赋了默认值,但是在m的右边有没有赋默认值的j
     int groucho(int k=1,int m=2,int j=3); //有效
    05)调用方法:(假如有函数harpo()的声明:int harpo(int n, int m=4, int j=5);)
     int beeps = harpo(2); //等价于harpo(2,4,5); m和j的默认值都不覆盖掉
     int beeps = harpo(2,8); //等价于harpo(2,8,5); 只是覆盖掉m的默认值
     int beeps = harpo(8,7,6); //等价于harpo(8,7,6); 全部覆盖掉默认值
     int beeps = harpo(8,,6); //不合法!!
    06)在函数定义的时候,默认参数不可以再次赋值!!只是在声明的时候说明一下就好了
    */

     1 #include <iostream>
     2 const int ArSize = 80;
     3 char* left(const char* str, int n = 1);  //声明一个函数,返回值为char*(一个字符串(地址)),有一个默认参数n,默认值为1
     4 
     5 int main()
     6 {
     7     using namespace std;
     8     char sample[ArSize];  //定义一个字符串数组
     9     cout << "请输入一个字符串:";
    10     cin.getline(sample, ArSize);  //输入到sample中,可输入的最大字符数为ArSize
    11     //cin.get(sample, ArSize);
    12     cout << "您输入的字符串为:" << sample << endl;
    13     char* ps = left(sample, 4);  //子函数调用,3将覆盖掉默认值
    14     cout << "子函数调用后截取的字符串为:" << ps << endl;
    15     delete[] ps;  //释放空间
    16 
    17     system("pause");
    18     return 0;
    19 }
    20 //传递给left()函数的第一个参数,只能是char型字符串或数组
    21 char* left(const char* str, int n)
    22 {
    23     char* pa = new char[n+1];  //只能定义一个指针,然后让该指针指向一个内存空间
    24     pa[n] = '';
    25     for (int i = 0; i < n; i++)
    26     {
    27         pa[i] = str[i];
    28     }
    29     return pa;
    30 }
    31 //char* left(const char* str, int n)  //在函数定义的时候,默认参数不可以再次赋值!!,只是在声明的时候说明一下就好了
    32 //{
    33 //    if (n < 0)
    34 //        n = 0;
    35 //    char* pa = new char[n + 1];
    36 //    int i;
    37 //    for (i = 0; i < n && str[i]; i++)
    38 //        pa[i] = str[i];
    39 //    while (i <= n)
    40 //        pa[i++] = '';  //设置其余的字符串为''
    41 //    return pa;
    42 //}
    使用默认参数截取一个字符串中的前几个值

    执行结果为:

     函数重载

    /*
    01)函数多态是指函数可以有多种形式,类似的函数重载是指可以有多个同名的函数,因此对名称进行了重载
     可以通过函数重载来设计一些列的函数---他们完成相同的工作,但使用不同的参数列表
    02)c++要通过上下文来确定使用的重载函数版本
    03)函数特征标:指的是函数的参数列表;如果两个函数的参数数目和类型都相同,且参数的排列顺序也相同
     则他们的特征标相同,而变量名是无关紧要的。
    04)c++允许定义名称相同的函数,条件是他们的特征标不相同。可以定义一组原型如下的print()函数
     void print(const char* str, int width); //#1
     void print(double d, int width); //#2
     void print(long l, int width); //#3
     void print(int i,int width); //#4
     void print(const char* str); //#5
     使用print()函数时,编译器将根据所使用的参数来使用相应特征标的原型
     print("Panckes",15); //use #1
     print("Syrup"); //use #5
     print(1999.0, 10); //use #2
     print(1999L, 15); //use #3
     遇到类型不匹配的怎么办?能对参数进行强制转换的,则进行强制转换,不可以则报错,如:
     unsigned int year = 3210;
     print(year,10); //发现print()函数的原型中没有第一个参数为unsigned int类型的,但是有第一个参数
     类型为double、long和int型的,但是有三个,如果有一个还可以进行强制转换,现在编译器不知道以哪个
     为原型进行强制转换,则编译器会报错。
    05)一个将类型引用与类型本身视为同一个特征标,如下
     double cube(double x); //#6
     double cube(double & x); //#7
     编译器将视这两个函数为一样的 因为cube(x)使用#6和#7都是可以的
    06)匹配函数时,并不区分const和非const变量,但是有const形参和const实参可以匹配到的,还是会首先匹配这样的
     如以下声明:
     void dribble(char* bits); //#8
     void dribble(const char* cbits); //#9
     //由于不区分const和非const变量,所以#8和#9是重复的
     void dabble(char* bits); //#10
     void drivel(const char* bits); //#11
     调用:
     const char p1[20] = "How is the weather?";
     char p2[20] = "How is business?";

     dribble(p1); //use #9
     dribble(p2); //use #8
     dabble(p1); //not match 因为p1是const类型,而dabble的参数为非const类型,将const类型赋给非const是非法的
     drivel(p1); //use #11 将const赋给const是合法的
     drivel(p2); //use #11 将非const赋给const是合法的
    07)重载函数的返回值类型可以不同,但是特征标必须不同,如以下不是函数重载
     long gronk(int n, float m);
     double gronk(int n, float m); 虽然返回值不同,但是特征标是一样的,所以c++不允许以这种方式重载gronk()
    08)重载引用参数,类设计和STL经常使用引用参数,因此知道不同引用类型的重载很重要。请看如下三个原型:
     void sink(double & r1); //左值引用参数r1可以与可修改的左值参数(如double变量)匹配
     void sank(const double & r2); //const左值引用参数r2可以与可修改的左值参数、const左值参数与右值参数(如两个double值的和)匹配
     void sunk(double && r2); //右值引用参数可以与右值参数匹配,如x+y  *****
     如果重载使用这三种参数的函数,结果调用最匹配的版本
     再如以下的声明:
     void staff(double & rs);
     void staff(const double & rcs);
     void stove(double & r1);
     void stove(const double & r2);
     void stove(double && r3);
     调用:
     double x = 55.5;
     const double y = 32.0;
     stove(x); //calls stove(double & r1)
     stove(y); //calls stove(const double & r2)
     stove(x+y); //calls stove(double && r3)
     如果没有定义stove(double && r3),则stove(x+y)将调用stove(const double & r2)
    */

     重载示例

    /*
    01)想要知道一个数字(n)有多少位,方法如下:
     unsigned int digits = 1;
     while(n/=10) //n/=10等价于n=n/10
       digits++;
     假如n=238,那么第一次n=238/10=23,digits=2
     第二次n=23/10=2,digits = 3
     第三次n=2/10=0退出循环,所以就判断数一个数字的位数
    02)现在已经知道一个数字有5位,要取出前三位,则将这个数除以10后再除以10,就可以得到所需的数值
     每除以10就删除数字的最后一位,要知道删除多少为,只需要将总位数减去需要获得的位数即可
     例如,要获得9位数的前四位,需要删除后面的5位,可以这样编写代码:
     ct = digits - ct; //其中digits为数字的总位数,ct为需要获得的数字位数
     while(ct--)
       num /= 10;
     return num;

    03)何时使用函数重载?
     仅当函数执行基本上相同的任务,但使用不同形式的数据时,才应使用函数重载
     使用函数重载只需编写一个函数,程序也只需为一个函数请求内存;需要修改函数时,也只需修改一个
     然后,如果需要使用不同类型的参数,则使用默认参数的话,就不能用了,这时应使用函数重载

    */

    //在该例程中,将上一节中的默认参数中的代码拿过来,并且将上面的代码放入left()函数中,于是就出现了两个
    //left()函数,用函数重载

     1 #include <iostream>
     2 
     3 unsigned long left(unsigned long num, unsigned ct); 
     4 char* left(const char* str, int n = 1);  //声明一个函数,返回值为char*(一个字符串(地址)),有一个默认参数n,默认值为1
     5 //声明两个left()函数,由于形参不一样(函数标不一样),所以为函数重载
     6 
     7 int main()
     8 {
     9     using namespace std;
    10     
    11     const char* trip = "Hawaii!!";  //声明一个指向一个字符串的指针trip
    12     unsigned long n = 12345678;  //声明一个无符号长型数据变量
    13     int i;
    14     char* temp;
    15 
    16     for (i = 1; i < 10; i++)
    17     {
    18         cout << left(n, i)<<endl;
    19         temp = left(trip, i);
    20         cout << temp << endl;
    21         delete[] temp;  //释放内存
    22     }
    23     system("pause");
    24     return 0;
    25 }
    26 //传递给left()函数的第一个参数,只能是char型字符串或数组
    27 //char* left(const char* str, int n)
    28 //{
    29 //    char* pa = new char[n+1];  //只能定义一个指针,然后让该指针指向一个内存空间
    30 //    pa[n] = '';
    31 //    for (int i = 0; i < n; i++)
    32 //    {
    33 //        pa[i] = str[i];
    34 //    }
    35 //    return pa;
    36 //}
    37 char* left(const char* str, int n)
    38 {
    39     if (n < 0)
    40         n = 0;
    41     char* pa = new char[n + 1];
    42     int i;
    43     for (i = 0; i < n && str[i]; i++)
    44         pa[i] = str[i];
    45     while (i <= n)
    46         pa[i++] = '';  //设置其余的字符串为''
    47     return pa;
    48 }
    49 unsigned long left(unsigned long num, unsigned ct)  //num为原始数据,ct为要取的位数
    50 {
    51     unsigned digits = 1;
    52     unsigned long n = num;  //备份数据
    53     if (num == 0 || ct == 0)
    54         return 0;
    55     while (n /= 10)
    56         digits++;  //获取数据的位数
    57     if (digits > ct)
    58     {
    59         ct = digits - ct; //获取要删除的位数
    60         while (ct--)
    61             num = num / 10;
    62         return num;
    63     }
    64     else
    65         return num;
    66 }
    函数重载示例(两个left()函数)

    执行结果:

     函数模板 

    /*
    01)函数模板的定义,如新建一个交换模板:
     template <typename AnyType>
     void swap(AnyType & a, AnyType & b)
     {
       AnyType temp;
       temp = a;
       a = b;
       b = temp;
     }
     其中关键字template和typename是必须的,关键字typename也可以用class来代替,另外必须使用尖括号
     类型名(AnyType)可以是任意的,大多数程序员都使用简单的名称,如T,函数模板并不创建任何函数,只是
     告诉编译器如何创建函数,算是一种解决方案
     调用方法为: swap(x,y); //假如x和y都是int类型的,那么在swap的定义中将会用int来代替AnyType
    */

     1 #include <iostream>
     2 
     3 //声明一个函数模板
     4 template <typename T>  //typename也可以用class代替
     5 void Swap(T & a, T & b);  //T可以看作是一个变量的类型名,如int、double等,估计是c++内部有swap函数,所以此处不能用小写的swap(),否则会报错
     6 
     7 int main()
     8 {
     9     using namespace std;
    10 
    11     int i, j;
    12     cout << "请输入一个整型数据:";
    13     cin >> i;
    14     cout << "请再次输入一个整型数据:";
    15     cin >> j;
    16     cout << "您输入的数据为:i=" << i << ", j=" << j << endl;
    17     Swap(i, j);  //函数模板调用,将会产生函数 swap(int & a, int & b)
    18     cout << "调用完模板函数之后 i=" << i << ", j=" << j << endl;
    19 
    20     cout << endl;
    21 
    22     double m, n;
    23     cout << "请输入一个double型数据:";
    24     cin >> m;
    25     cout << "请再次输入一个double型数据:";
    26     cin >> n;
    27     cout << "您输入的数据为:m=" << m << ", n=" << n << endl;
    28     Swap(m, n);  //函数模板调用,将会产生函数 swap(double & a, double & b)
    29     cout << "调用完模板函数之后 m=" << m << ", n=" << n << endl;
    30 
    31     system("pause");
    32     return 0;
    33 }
    34 
    35 //函数模板的定义
    36 template <typename T>  
    37 void Swap(T & a, T & b)
    38 {
    39     T temp = a;  //新建一个变量temp 这里还必须是在新建temp的时候就对起进行初始化,否则也是会报错
    40     //a = temp;  //暂时保存a的数据
    41     a = b;  //交换数据
    42     b = temp;
    43 }
    一个函数模板声明、定义和调用方法

    //函数说明:此函数模板不能缩短可执行程序,对于以上程序,最终仍将由两个独立的函数定义,就像以手工的
    //方式定义了这些函数,最终的代码不包含任何模板,而只包含了为程序生成的实际函数
    //更常见的是,将木办法放在头文件中

    执行结果为:

     重载的模板 

    /*
    01)复习之函数重载:函数重载就是几个函数可以有相同的函数名字,返回值也可以相同,但是形参的类型一定是不相同的
    02)并非所有的类型都相应相同的算法,为满足这种要求,可以像重载常规函数定义那样重载重载模板定义,被重载
      的模板的函数特征标(即形参的类型)必须不同
    */

     1 #include <iostream>
     2 
     3 template <typename T>
     4 void Swap(T & a, T & b);  //新建一个函数模板  注意两个模板可以使用一个T
     5 
     6 template <typename T>
     7 void Swap(T* a, T* b, int n);  //声明一个函数模板,注意最后的形参n,并非所有的模板都是用模板型参数
     8 
     9 void show(int a[]);  //声明一个普通函数
    10 const int Lim = 8;
    11 
    12 int main()
    13 {
    14     using namespace std;
    15 
    16     int i, j;
    17     cout << "请输入一个整型数据:";
    18     cin >> i;
    19     cout << "请再次输入一个整型数据:";
    20     cin >> j;
    21     cout << "您输入的数据为:i=" << i << ", j=" << j << endl;
    22     Swap(i, j);  //调用模板Swap(T & a, T & b),将会产生函数 swap(int & a, int & b)
    23     cout << "调用完模板函数之后 i=" << i << ", j=" << j << endl;
    24 
    25     int d1[] = { 0,7,0,4,1,7,7,6 };
    26     int d2[] = { 0,7,2,0,1,9,6,9 };
    27     cout << "在调用Swap(T* a, T* b, int n)之前d1矩阵和d2矩阵中的值分别为:" << endl;
    28     cout << "d1=";
    29     show(d1);
    30     cout << "d2=";
    31     show(d2);
    32     cout << "在调用Swap(T* a, T* b, int n)之后d1矩阵和d2矩阵中的值分别为:" << endl;
    33     Swap(d1, d2, Lim);  //调用模板Swap(T* a, T* b, int n),d1和d2都是数组名,数组名是数组内第一个元素的地址,所以这样调用时可以的
    34     cout << "d1=";
    35     show(d1);
    36     cout << "d2=";
    37     show(d2);
    38 
    39     system("pause");
    40     return 0;
    41 }
    42 
    43 //第一个Swap()函数模板的定义
    44 template <typename T>
    45 void Swap(T & a, T & b)
    46 {
    47     T temp = a;  //新建一个变量temp 这里还必须是在新建temp的时候就对起进行初始化,否则也是会报错
    48     //a = temp;  //暂时保存a的数据
    49     a = b;  //交换数据
    50     b = temp;
    51 }
    52 
    53 //第二个Swap()函数模板的定义
    54 template <typename T>
    55 void Swap(T* a, T* b, int n)
    56 {
    57     for (int i = 0; i < n; i++)
    58     {
    59         int temp;
    60         temp = a[i];
    61         a[i] = b[i];
    62         b[i] = a[i];
    63     }
    64 }
    65 
    66 void show(int a[])
    67 {
    68     using namespace std;
    69     for (int i = 0; i < Lim; i++)
    70         cout << a[i] << ",";
    71     cout << endl;
    72 }
    两个Swap()函数---使用重载和函数模板

    执行结果:

    显式具体化 

    /*
    01)对于给定函数名,可以有非模板函数、模板函数、显式具体化模板函数和他们的重载版本
    02)显式具体化的声明(原型)和定义应使用template开头,并通过名称来指出类型
    03)具体化优先于常规模板,而非模板函数优先于具体化和常规模板
    04)举例:
     //非模板函数声明
     void Swap(job &, job &); //注意job是一个结构名

     //模板函数声明
     template <typename T>
     void Swap(T &, T &); 注意声明可以将形参的名字给省略,但是定义的时候就必须要有形参的名字了

     //具体化声明
     template <> void Swap<job>(job &, job &); //job也是一个结构的名字,而且这里的<job>是可选的
     //加上<job>是说明是job的一个具体化
     //或具体化声明
     template <> void Swap<int>(int & a ,int & b); //这里表明是对int的具体化
    05)正如前面所指出的,如果有多个函数原型,则编译器在选择原型时,非模板版本优先于显式具体化和模板版本
     而显式具体化优先于使用模板生成的版本
    */

     1 //显式具体化实例
     2 #include <iostream>
     3 
     4 //声明模板
     5 template <typename T>
     6 void change(T & a, T & b);
     7 //新建一个结构
     8 struct job
     9 {
    10     char name[40];
    11     double salary;
    12     int floor;
    13 };
    14 //声明一个显式具体化
    15 template<> void change<job>(job & a, job & b);  //a,b为指向结构job的引用
    16 //声明普通显示函数
    17 void show(job & a);  //形参为指向结构job的引用a
    18 //主函数
    19 int main()
    20 {
    21     using namespace std;
    22     cout.precision(2);  //显示小数点后两位
    23     cout.setf(ios_base::fixed, ios_base::floatfield);  //fixed表示将对象cout置于定点表示模式,floatfield表示输出按浮点格式,小数点后有6位    
    24     
    25     //模板函数change(T & a, T & b)的调用
    26     int i = 10, j = 20;
    27     cout << "i,j = " << i << "," << j << endl;
    28     cout << "使用模板函数change(T & a, T & b):" << endl;
    29     change(i, j);
    30     cout << "i,j = " << i << "," << j << endl;
    31 
    32     //显式具体化函数模板template<> void change<job>(job & a, job & b)的调用
    33     job sue = { "Susan Yaffee",7300.60, 7 };
    34     job sideny = { "Sideny Taffee",78060.72, 9 };
    35     cout << "在没有调用显式具体化函数模板之前:" << endl;
    36     show(sue);
    37     show(sideny);
    38     change(sue, sideny);
    39     cout << "在没有调用显式具体化函数模板template<> void change<job>(job & a, job & b)之后:" << endl;
    40     show(sue);
    41     show(sideny);
    42 
    43     system("pause");
    44     return 0;
    45 }
    46 //模板定义
    47 template <typename T>
    48 void change(T & a, T & b)
    49 {
    50     T temp;
    51     temp = a;
    52     a = b;
    53     b = temp;
    54 }
    55 //显式具体化实例
    56 template<> void change<job>(job & a, job & b)
    57 {
    58     double temp1;
    59     int temp2;
    60     temp1 = a.salary;  //将a,salary数据保存
    61     a.salary = b.salary;  //将b,salary数据赋值给a,salary
    62     b.salary = temp1;
    63     temp2 = a.floor;
    64     a.floor = b.floor;
    65     b.floor = temp2;
    66 }
    67 //普通函数定义
    68 void show(job & a)
    69 {
    70     using namespace std;
    71     cout << a.name << ": $" << a.salary << " on the floor " << a.floor << endl;
    72 }
    显式具体化实例,给显式具体化函数形参为结构

    执行结果为:

     实例化和具体化(包含的知识点为结构数组,指针数组,模板等)

    /*
    01)隐式实例化、显式实例化和显式具体化统称为具体化
    02)在代码中包含模板本身并不会生成函数定义,它只是一个用于生成函数定义的方案。编译器在使用模板为
     特定类型生成函数定义时,得到的是模板的实例。例如在主函数找调用Swap(i,j)将胡会导致编译器产生一个
     Swap()的实例该实例使用int类型。模板并非是函数定义,但使用int的模板实例就是函数定义了。
    03)举例:
       //隐式实例化
      template <typename T>
      void change(T & a, T & b); //隐式实例化的声明
      ...
      int main()
      {
       ...
       int x=10,y=20;
       change(x,y); //隐式实例化模板的调用,此时变化生成函数定义,即实例化
       ...
      }
      //显式实例化
      template void change<int>(int & a, int & b);
      //显式具体化
      template<> void change<int>(int & a, int & b); //change后的<int>是可以省略的
      区别在于,这些声明的意思是"不要使用change()模板来生成函数定义,而应使用专门为int
      类型显式的定义的函数定义"这些声明必须有自己的函数定义。显式具体化的声明在关键字
      template后包含<>,而显式实例化没有。
    04)还可以通过在程序找使用函数来创建显式实例化,例如:
       template <typename T> //template也可以用class代替
       T Add(T a, T b); //声明一个返回值为T的函数Add()
    05)int m=6; double x=10.2;
     Add(x,m); //调用上面声明的Add()函数,但是x和m的类型是不一样的,因为模板Add()找要求
     该函数的两个形参的类型必须是一样的,但,可以通过强制转换变换成一样的,即将m强制转换为double
     类型,以便于第二个参数匹配。
    06)关于引用:
     double m=12.2;
     int & ra = m; //是不对的!!!因为类型不匹配
    07)在同一个文件中使用同一类型的显示实例化和显式具体化将会出错
     比如:
     template<> void change<int>(int&, int&);
     template<> void change(int&, int&); //是不允许的!!!
    */

     1 ...
     2 //(隐式)模板声明
     3 template <typename T>  
     4 void change(T & a, T & b);  //void change(T &, T &); 声明的时候不带名字也是可以的
     5 //显式具体化的声明
     6 template <> void change<job>(job & a, job & b);
     7 
     8 int main()
     9 {
    10     //显式实例化声明
    11     template void change<char>(char &str1, char & str2);
    12 
    13     short a, b;
    14     change(a, b);  //调用(隐式)模板声明void change(T & a, T & b);
    15 
    16     job n, m;
    17     change(n, m);  //调用显式具体化的声明template <> void change<job>(job & a, job & b);
    18 
    19     char g, h;
    20     change(g, h);  //调用显式实例化声明template void change<char>(char &str1, char & str2);
    21 }
    一些概念的总结
     1 //新建一个包含三个指针的数组,并给该数组赋值(赋地址),作为函数参数
     2 #include <iostream>
     3 
     4 //新建一个模板A
     5 template <typename T>
     6 void ShowArray(T arr[], int n);  //新建一个函数模板,形参为一个数组和int型变量
     7 
     8 //新建一个模板B
     9 template <typename T>
    10 void ShowArray(T* arr[], int n);  //T* arr[](如果T用int代替即int* arr[]):由于[]的优先级比*大,所以T* arr[]的意思是新建一个数组,里面包含了若干个指针
    11 
    12 //新建一个结构
    13 struct debts
    14 {
    15     char name[50];
    16     double amount;
    17 };
    18 
    19 int main()
    20 {
    21     using namespace std;
    22 
    23     int things[6] = { 13,31,103,310,130 };
    24     
    25     //使用结构debts新建一个结构数组mr_E[3],并对其进行初始化
    26     struct debts mr_E[3] =    //struct可以省略
    27     {
    28         {"Ima Wolfe",2400.0},
    29         {"Ura Foxe",1300.0},
    30         {"Iby Stout",1800.0}
    31     };  //注意这里还要有一个分号的,软件不会自己给加上
    32     double* pd[3];  //由于[]的优先级比*高,所以此句的意思是新建一个数组,里面包含了三个指针
    33     //依次对数组pd中的指针赋值
    34     for (int i = 0; i < 3; i++)
    35         pd[i] = &mr_E[i].amount;  //依次将结构矩阵mr_E[i]中的一个成员amount的地址赋给pd[i]
    36     cout << "先将参数things[]传入函数模板ShowArray" << endl;
    37     ShowArray(things, 6);
    38     cout << "再将参数pd传入函数模板ShowArray" << endl;
    39     ShowArray(pd, 3);  //pd是一个矩阵的名字,该矩阵内的元素全为指针
    40 
    41     system("pause");
    42     return 0;
    43 }
    44 
    45 //模板A的定义
    46 template <typename T>
    47 void ShowArray(T arr[], int n)
    48 {
    49     using namespace std;
    50     cout << "模板A:" << endl;
    51     for (int i = 0; i < n; i++)
    52         cout << arr[i] << ' ';
    53     cout << endl;
    54 }
    55 //模板B的定义
    56 template <typename T>
    57 void ShowArray(T* arr[], int n)
    58 {
    59     using namespace std;
    60     cout << "模板B:" << endl;
    61     for (int i = 0; i < n; i++)
    62         cout << *arr[i] << ' ';
    63     cout << endl;
    64 }
    新建一个指针数组,并使该数组内的指针指向结构数组中的元素,并且用到了函数模板

    执行结果为:

    自己选择使用哪个函数模板 

    /*
    01)在有些情况下,可以通过编写何时的函数调用,引导编译器做出自己希望的选择。
    02)以下代码将模板函数定义放在文件开头,从而无需提供模板原型
    */

     1 #include <iostream>
     2 
     3 //在main函数之前定义函数模板,从而无需声明  #1
     4 template <typename T>
     5 T lesser(T a, T b) //定义一个返回值类型为T的函数,有两个形参,类型也分别为T
     6 {
     7     using namespace std;
     8     cout << "use #1" << endl;
     9     return a < b ? a : b;  //如果a<b成立,那么返回a,否则返回b
    10 }
    11 //定义一个常规函数  #2
    12 int lesser(int a, int b)
    13 {
    14     using namespace std;
    15     cout << "use #2" << endl;
    16     a = a < 0 ? -a : a;  //如果a<0成立,那么将a取反,否则保持a的原型
    17     b = b < 0 ? -b : b;
    18     return a < b ? a : b;
    19 }
    20 int main()
    21 {
    22     using namespace std;
    23 
    24     int m = 20;
    25     int n = -30;
    26     double x = 15.5;
    27     double y = 25.9;
    28 
    29     cout << lesser(m, n) << endl;  //此时#1和#2都是符合该函数调用的,此时编译器选择常规函数调用,即使用#2
    30     cout << lesser(x, y) << endl;  //use #1 with double
    31     cout << lesser<>(m, n) << endl;  //use #1 with int,其中lesser后面的<>表示使用模板函数
    32     cout << lesser<int>(x, y) << endl;  //use #1 with int ,但是x,y的类型为double,此处将x和y进行强制转换为int类型
    33                                        //且该语句要求进行的是显式实例化
    34 
    35     system("pause");
    36     return 0;
    37 }
    自己选择使用常规函数还是是用模板

    执行结果:

     模板函数的优化,设计到相关函数的引进 m18

    /*
    01)类型的不确定
     template <typename T1,typename T2>
     void ft(T1 x,T2 y)
     {
       xpy = x+y; //由于x是由T1定义的,y是由T2定义的,那么xpy是什么类型呢?
     }
     A 假如T1是double类型,T2是int类型,那么xpy是double类型
     B 假如T1是short类型,T2是int类型,那么xpy是int类型
     C 假如T1是short类型,T2是char类型,那么xpy将导致和的类型自动提升,xpy为int类型
     结论:在c++98中没有办法声明xpy的类型
    02)xpy类型不确定的解决办法---使用decltype(c++11)
     例:decltype(x) y; //定义变量y,并且将y的类型定义为x的类型
     给decktype提供的参数可以是表达式,因此在前面的模板函数ft()中,可以使用下面的代码:
     decltype(x+y) xpy; 将xpy的类型设置为何x+y的类型是一样的
     xpy = x+y; //赋值操作
     另一种方法是:
     decltype(x+y) xpy = x+y; //合为一句
    03)假如有右边的声明: decltype(expression) var;
     A 如果expression是一个没有用括号阔气的标识符,则car的类型与该标识符的类型相同,也包括const等限定符
      double x = 5.5;
      double y = 7.9;
      double & rx = x;
      const double* pd;
      decltype(x) w; //声明一个变量w,类型为double
      decltype(rx) u =y; //声明一个变量u,类型为double &,并为u赋初值为y,即u是一个引用,指向y
      decltype(pd) v; //声明一个指针变量v,类型为double*
     B 如果expression是一个函数调用,则var的类型与返回类型相同
      long indeed(int);
      decltype(indeed(3)) m; //声明一个变量m,类型为long
         注意:这里并不会实际调用函数,编译器通过查看函数的原型来获悉返回类型,而无需实际调用函数
     C 如果expression是一个坐直,则var为指向其类型的引用,注意在这种情况下expression必须是用括号
      括起来的标识符,举例如下:
      double xx = 4.4;
      decltype((xx)) r2 = xx; //声明一个变量r2,类型为double &,并为r2赋初值指向xx,
                    //且因为是用括号括起来的,所以r2是一个引用
                   //切不会影响xx的使用
      decltype(xx) w = xx; //声明一个变量w,类型为double,
               //这里w不再是引用是因为decltype括号找的xx未用括号括起来,所以此时的w不是引用
    D 如果expression前面的条件都不满足,则var的类型与expression类型相同
     int j = 3;
     int & ra = j; //声明一个引用ra 指向j 即ra和j是等价的
     int & rn = j; //声明一个引用rn 指向j 即rn和j是等价的
     decltype(j+6) i1; //i1的类型为int
     decltype(100L) i2; //i2的类型为long
     decltype(ra+rn) i3; //i3的类型为int,注意虽然ra和rn都是引用,但是k+n不是引用,他是两个int的和
    04)还有一种是decltype无法解决的,如下:
     template <typename T1,typename T2>
     ?type? gt(T1 x, T2 y)
     {
           return x+y;
     }
     那么?type?处的类型应该是什么?此时c++11提新增了一种声明和函数定义,如下
     double h(int x,int y); 可以写成如下:
     auto h(int x,int y) ->double;
     其中auto是一个占位符,->double被称为后置返回类型
     那么gt()函数返回值的类型就可以解决了,方法如下:
     template <typename T1,typename T2>
     auto gt(T1 x, T2 y) ->decltype(x+y) //此时decltype在函数声明后,因为x和y位于作用于内,可以使用他们
     {
       return x+y;
     }

    */

    2019.03.27 晚 haijing in HZ miss you.

  • 相关阅读:
    nginx
    vue拦截
    时间转化封装
    Vue粒子特效(vue-particles插件)
    vscode 使用ESLint 自动检查,保存时自动格式化
    小程序请求封装
    common.js
    h5常见
    封装promise
    promise使用
  • 原文地址:https://www.cnblogs.com/YiYA-blog/p/10546023.html
Copyright © 2020-2023  润新知