C++ Primer Plus 笔记第八章

  

C++内联函数:

   内联函数的编译代码与其他程序代码 “内联” 起来了,编译器将使用相应的函数代码替换函数调用;

   对于内联函数,程序无需跳到另一个位置处执行代码,再跳回来:

    内联函数的运行速度比常规函数稍快;

    但是需要占用更多的内存

   使用内联函数:

    1. 在函数声明前加上关键字 inline;

    2. 在函数定义前加上关键字 inline;

    3. 通常做法是省略原型,将整个定义(函数头和所有函数代码)放在本应提供原型的地方 

   内联函数与常规函数一样,也是按值传递参数

引用变量:

   C++新增了一种复合类型——引用变量,引用是已定义的变量的别名

   引用变量的主要用途是用作函数的形参,通过引用变量用作参数,函数使用原始数据,而不是拷贝数据;

创建引用变量:

   int rats;

   int & rodents = rats;  // 此处引用声明允许将 rats 和 radents 互换——他们指向相同的值和内存单元 

   int & 表示的是指向 int 的引用

   记住:必须在声明引用时将其初始化(所以引用更接近与 const 指针)

将引用用作函数参数:

   按引用传递允许被调用函数能够访问调用函数中的变量(C语言只能按值传递(还可以指针)——拷贝);

   参数传递对比:

    调用:

        swapr ( wallet1, wallet2);    // 引用传递调用

        swapp ( &wallet1, &wallet2 );  // 指针传递调用

        awapv (wallet1, wallet2);    // 按值传递调用

    原型:

        void ( int & a, int & b);        // 引用调用原型,变量a,b是 wallet1 和 wallet2 的别名

        void ( int * a, int * b);       // 指针传递原型,需要在函数使用 p 和 q 的整个过程使用解除引用操作符 *

        void ( int a, int b);            // 按值传递原型,变量 a,b 是参数 wallet1 和 wallet2 的拷贝,互补影响

   被调用函数中引用变量修改,会改变调用函数中的值,如不想修改则应使用常量引用:

    double refcube ( const double &ra );  // 这样做,当编译器发现代码修改了 ra 的值时,将发生错误消息

临时变量、引用参数和 const:

   如果实参与形参不匹配,C++将生成临时变量(仅当参数为 const 引用时):

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

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

尽可能使用 const:

   1. 使用 const 可以避免无意中修改数据的编程错误;

   2. 使用 const 使函数能够处理 const 和非 const 实参,否则将只能接受非 const 数据;

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

何时使用引用参数:

   使用引用参数的原因:

    1. 程序员能够修改调用函数中的数据对象;

    2. 通过传递引用而不是整个数据对象,可以提高程序运行速度(数据对象较大时更重要)

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

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

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

    3. 如果数据对象是较大的结构,则使用 const 指针或 const 引用,可以节省复制结构所需的时间和空间;

    4. 若果数据对象是类对象,则使用 const 引用。传递类对象参数的标准方式是按引用传递

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

    1. 如果数据对象是内置数据类型,则使用指针;

    2. 如果数据对象是数组,则只能使用指针;

    3. 如果数据对象是结构,则使用引用或指针;

    4. 如果数据对象是类对象,则使用引用

将引用用于结构:

   引用非常适用于结构和类(C++的用户自定义类型),引入引用主要是为了用于这些类型,而不是基本的内置类型;

   例程:

 1 #include<iostream>
 2 using namespace std;
 3 struct sysop {
 4     char name[26];
 5     char quote[64];
 6     int used;
 7 };
 8 const sysop & use(sysop & sysopref);
 9 
10 int main()
11 {
12     sysop looper = 
13     {
14         "Rick \"Fortan\" Looper",
15         "I`m a goto kind of guy.",
16         0
17     };
18 
19     use(looper);
20     cout << "Looper: " << looper.used << " use(s)\n";
21 
22     sysop copycat;
23     copycat = use(looper);
24     cout << "Looper: " << looper.used << " use(s)\n";
25     cout << "copycat: " << copycat.used << " use(s)\n";
26     cout << "use(looper): " << use(looper).used << " use(s)\n";
27 
28     return 0;
29 }
30 
31 const sysop & use(sysop & sysopref)
32 {
33     cout << sysopref.name << " says:\n";
34     cout << sysopref.quote << endl;
35     sysopref.used++;
36     return sysopref;
37 }

    函数说明:

    1. 使用指向结构的引用

      use ( looper );

      函数调用结构 looper 按引用传递给 use() 函数,使得 sysopref 成为 looper 的别名

    2. 将引用作为返回值

      通常,返回机制将返回值复制到临时存储区域中,随后调用程序将访问该区域;

      返回引用意味着调用程序将直接访问返回值,而不需要拷贝;

      通常,引用将指向传递给函数的引用,因此调用函数实际上是直接访问自己的一个变量

    记住: 返回引用的函数实际上是被引用变量的别名

    3. 使用函数调用来访问结构成员

      cout << "use(looper): " << use(looper).used << " use(s)\n;

      函数 use() 返回一个指向 looper 的引用,因此上述代码与下面两行代码等效:

      use ( looper );

      cout  << " use(looper):  " << looper.used << " use(s) \n ";

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

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

    1. 返回一个作为参数传递给函数的引用,将指向调用函数使用的数据,因此返回的引用也指向这些数据

    2. 使用 new 来分配新的存储空间:

      sysop * psysop = new sysop;

 为何将 const 用于引用返回类型:

   const sysop & use(sysop & sysopref);

   const sysop & 表示不能使用返回的引用来直接修改它指向的结构

   省略 const 的情况:

    use ( looper ).used = 10;

    由于 use() 返回一个指向 looper 的引用,上述代码将与下面的代等效:

    use ( looper );

    looper.used = 1;

     省略 const 后,可以编写更简短但含义更模糊的代码

   通常,将返回类型声明为 const 引用,可以减程序的模糊特性

将引用用于类对象:

   将类对象传递给函数时,C++通常做法是使用引用。例如可以通过使用引用,让函数将类 string、ostream、istream、ofstream和ifstream等类对象作为参数

   将引用用于 string 类例程: 

 1 #include<iostream>
 2 #include<string>
 3 using namespace std;
 4 
 5 string version1(const string & s1, const string & s2);
 6 const string & version2(string & s1, const string & s2);
 7 //const string & version3(string & s1, const string & s2);
 8 
 9 int main()
10 {
11     string input;
12     string copy;
13     string result;
14 
15     cout << "Enter a string: ";
16     getline(cin, input);
17     copy = input;
18     cout << "Your string as entered: " << input << endl;
19     result = version1(input, "***");
20     cout << "Your string enhanced: " << result << endl;
21     cout << "Your original string: " << input << endl;
22 
23     result = version2(input, "***");
24     cout << "Your string enhanced: " << result << endl;
25     cout << "Your original string: " << input << endl;
26 
27     return 0;
28 }
29 
30 string version1(const string & s1, const string & s2)
31 {
32     string temp;
33     temp = s2 + s1 + s2;
34     return temp;
35 }
36 
37 const string & version2(string & s1, const string &s2)
38 {
39     s1 = s2 + s1 + s2;
40     return s1;
41 } 

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

    如果形参类型为 const string &,在调用函数时,使用的实参可以是 string 对象或C-风格字符串:

      因此代码 result = version1(input, "***");  将 "***" 赋给string对象是可行的

对象、继承和引用:

   将特性从一个类传递给另一个类的语言特性被称为继承

   ostream 是基类,ofstream 是派生类,派生类继承了基类方法,意味着 ofstream 可以使用基类的特性,如格式化方法 precision(),setf();

   基类引用可以指向派生类对象,而无需进行强制类型转换:

    可以定义一个接收基类引用作为参数的函数,调用该函数时,可以将基类对象作为参数也可以将派生类对象作为参数;

    参数类型为 ostream & 的函数可以接受 ostream 对象(如 cout)或自己声明的 ofstream 对象作为参数

   例程:

 1 #include<iostream>
 2 #include<fstream>
 3 #include<cstdlib>
 4 using namespace std;
 5 
 6 void file_it(ostream & os, double fo, const double fe[], int n);
 7 const int LIMIT = 5;
 8 
 9 int main(void)
10 {
11     ofstream fout;
12     const char * fn = "ep-data.txt";
13     fout.open(fn);
14     if (!fout.is_open())
15     {
16         cout << "Can`t open " << fn << ". Bye.\n";
17         exit(EXIT_FAILURE);
18     }
19     double objective;
20     cout << "Enter the focal length of your telescope objective in mm: ";
21     cin >> objective;
22     double eps[LIMIT];
23     cout << "Enter the focal lengths, in mm, of " << LIMIT << " eyepieces:\n";
24     for (int i = 0; i < LIMIT; i++)
25     {
26         cout << "Eyepiece #" << i + 1 << ": ";
27         cin >> eps[i];
28     }
29     file_it(fout, objective, eps, LIMIT);
30     file_it(cout, objective, eps, LIMIT);
31     cout << "Done\n";
32     return 0;
33 }
34 
35 void file_it(ostream & os, double fo, const double fe[], int n)
36 {
37     ios_base::fmtflags initial;
38     initial = os.setf(ios_base::fixed);  // save initial formatting state
39     os.precision(0);
40     os << "Focal length of objective: " << fo << " mm\n";
41     os.setf(ios::showpoint);
42     os.precision(1);
43     os.width(12);
44     os << "f eyepiece";
45     os.width(15);
46     os << "magnification" << endl;
47     for (int i = 0; i < n; i++)
48     {
49         os.width(12);
50         os << fe[i];
51         os.width(15);
52         os << int(fo / fe[i] + 0.5) << endl;
53     }
54     os.setf(initial);  // restore initial formatting state
55 }

  file_it (fout, objective, eps, LIMIT);  // 将目镜数据写入到文件 ep-data.txt 中

  file_it (cout, objective, eps, LIMIT);      // 将目镜数据显示到屏幕上   

   程序演示了如何使用 ostream 类中的格式化方法:

    方法 setf() 能够设置各种格式化状态:

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

      setf (ios_base::showpoint) 将对象置于显示小数点的模式

    方法 precision() 指定此案时多少位小数(假定对象处于定点模式下);

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

   每个对象都存储了自己的格式化设置,因此,当程序将 cout 或者 fout 传递给 file_it 时,先修改格式化设置再恢复

默认参数:

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

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

      默认参数值是初始化值,因此原型将 n 初始化为 1,调用函数 left 如果省略参数 n,则它的值为1;

      如果没有省略参数 n 的传递,则传递值将覆盖默认参数值

   对于带参数列表的函数,必须从右向左添加默认值;

   实参按从左到右的顺序依次被赋给相应的形参;

   通过使用默认参数可以减少要定义的析构函数、方法以及方法重载的数量

函数重载:

   函数多态(函数重载),让我们可以使用多个同名的函数:

    可以通过函数重载来设计一系列函数——完成相同的工作,但使用不同的参数列表;

   函数重载的关键是函数的参数列表——也称为函数特征标

   需要注意,是特征标而不是函数类型使得可以对函数进行重载

   函数重载例程:

 1 #include<iostream>
 2 using namespace std;
 3 unsigned long left(unsigned long num, unsigned ct);   // 处理整数
 4 char * left(const char * str, int n = 1);        // 处理字符串
 5 
 6 int main()
 7 {
 8     char * trip = "Hawaii!! ";
 9     unsigned long n = 12345678;
10     int i;
11     char *temp;
12     for (i = 1; i < 10; i++)
13     {
14         cout << left(n, i) << endl;
15         temp = left(trip, i);
16         cout << temp << endl;
17         delete[] temp;
18     }
19     return 0;
20 }
21 unsigned long left(unsigned long num, unsigned ct)
22 {
23     unsigned digits = 1;
24     unsigned long n = num;
25 
26     if (ct == 0 || num == 0)
27         return 0;
28     while (n /= 10)
29         digits++;
30     if (digits > ct)
31     {
32         ct = digits - ct;
33         while (ct--)
34             num /= 10;
35         return num;
36     }
37     else
38         return num;
39 }
40 
41 char * left(const char * str, int n)
42 {
43     if (n < 0)
44         n = 0;
45     char *p = new char[n + 1];
46     int i;
47     for (i = 0; i < n && str[i]; i++)
48         p[i] = str[i];
49     while (i <= n)
50         p[i++] = '\0';
51     return p;
52     
53 }

 何时使用函数重载:

   仅当函数基本上执行相同的任务,但不使用不同形式的数据时,才应采用函数重载

 函数模板:

   函数模板是通用函数描述——使用通用类型来定义函数;

   通过将类型作为参数传递给模板,可以使编译器生成该类型的函数;

   建立交换模板:

    template <class Any>                     // template <typename Any>

    void Swap (Any &a,Any &b)

    {

      Any temp;

      temp = a;

      a = b;

      b = temp;

    }

 重载的模板:

   并非所有的类型都使用相同的算法,可以像重载常规函数定义那样重载模板定义;

   被重载的模板的函数特征标必须不同;

    template <class Any>

    void Swap (Any &a,Any &b);

 

    template <class Any>

    void Swap (Any *a,Any *b,int n);

 显示具体化:

   可以提供一个具体化函数定义——称为显示具体化;

   当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不再寻找模板;

   C++标准选择的具体化方法:

    1. 对于给定的函数名,可以有非模板函数、模板函数和显示具体化模板函数以及他们的重载版本;

    2. 显示具体化的原型和定义应以 template<> 打头,并通过名称来指出类型;

    3. 具体化将覆盖常规模板,而非模板函数将覆盖具体化的原型

     void Swap (job &,job &);                                // 非模板函数原型

    template <class Any>

    void Swap (Any &,Any &);                              // 模板函数原型

    template <> void Swap<job> (job &,job &);    // 具体化原型

 实例化和具体化:

   模板并非函数定义,编译器使用模板为特定类型生成函数定义时,得到的是模板实例:

    函数调用 Swap(i,j) 导致编译器生成一个 Swap() 的一个实例——隐式实例

    还可以直接命令编译器创建特定的实例,如 Swap<int>() ——显示化实例

      temeplate void Swap<int> (int,int);

   显示具体化使用下面两个等价声明之一:

    template <> void Swap <int> (int &,int &);

    template <> void Swap ( int &,int &);

   

   

猜你喜欢

转载自www.cnblogs.com/kidycharon/p/9705316.html