8.1 C++内联函数
内联函数时C++为提高程序运行速度所做的一项改进,常规函数和内联函数的主要区别不在于编写方式,而在于C++编译器如何将它们组合到程序中。
编译过程的最终产品是可执行程序——由一组机器语言指令组成。运行程序时,操作系统将这些指令载入到计算机内存中,因此每条指令都有特定的内存地址。执行常规函数调用时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈,跳到标记函数起点的内存单元,执行函数代码(也许还需将返回值放入寄存器中),然后跳回到地址被保存的指令处,来回跳跃需要一定的开销。
对于内联函数,编译器将使用相应的函数代码替换函数调用,程序无需跳到另一个位置处执行代码,再跳回来。因此,内联函数的运行速度比常规函数稍快,但代价时需要占用更多的内存。
如果执行函数代码的时间比处理函数调用机制的时间长,则节省的时间只占很小一部分。如果执行代码时间很短,则内联调用可节省相对来说的大部分时间,但节省的时间绝对值并不大,除非该函数经常被调用。
写代码时,通常做法,是省略函数原型,直接将整个函数定义放在main()的前面,并在函数定义前加上关键字inline。该内联函数不能过长,且不能递归。
1 #include<iostream>
2 using namespace std;
3
4 inline double square(double x) //inline表示内联函数
5 {
6 return x*x;
7 }
8
9 int main()
10 {
11 double a, b, c = 13.0;
12 a = square(5.0);
13 b = square(4.5 + 7.5);
14 cout << "a=" << a << ",b=" << b << endl;
15 cout << "c=" << c << ",c square=" << square(c++) << endl;
16 cout << "Now c=" << c << endl;
17 system("pause");
18 return 0;
19 }
8.2 引用变量
8.2.1 创建引用变量
引用是已定义的变量的别名。引用变量的主要用途是用作函数的形参,通过将引用变量作为参数,函数将使用原始数据,而不是其副本。这样除指针外,引用也为函数处理大型结构提供了一种非常方便的途径,同时对于设计类来说,引用也是必不可少的。
必须在声明引用变量时进行初始化。
1 #include<iostream>
2 using namespace std;
3
4 int main()
5 {
6 int rates = 101;
7 int &rodents = rates; //将rodents的类型声明为int&,即指向int变量的引用
8 cout << "rates= " << rates;
9 cout << ",rodents= " << rodents << endl;
10 rodents++;
11 cout << "rates= " << rates;
12 cout << ",rodents= " << rodents << endl;
13 cout << "rates address= " << &rates << endl;
14 cout << "rodents address= " << &rodents << endl;
15 cout << "****************************" << endl;
16 int bunnies = 50;
17 rodents = bunnies;
18 cout << "bunnies= " << bunnies;
19 cout << ",rates= " << rates;
20 cout << ",rodents= " << rodents << endl;
21 cout << "rates address= " << &rates << endl; //rates和rodents的地址相同
22 cout << "rodents address= " << &rodents << endl;
23 cout << "bunnies address= " << &bunnies << endl;
24
25 system("pause");
26 return 0;
27 }
将rodents初始化为*pt使得rodents指向rates,接下来将pt改为指向bunnies,并不能改变rodents引用的是rates。
1 #include<iostream>
2 using namespace std;
3
4 int main()
5 {
6 int rates = 101;
7 int *pt = &rates;
8 int &rodents = *pt;
9 cout << rates << ' ' << rodents << ' ' << *pt << endl;
10 int bunnies = 50;
11 pt = &bunnies;
12 cout << rates << ' ' << rodents << ' ' << *pt << endl;
13 system("pause");
14 return 0;
15 }
8.2.2 将引用用作函数参数
按引用传递:使得函数中的变量名成为调用程序中的变量的别名。
按引用传递允许被调用的函数能够访问调用函数中的变量。
8.2.3 引用的属性和特别之处
临时变量、引用参数和const
如果实参与引用参数不匹配,仅当参数为const引用时,C++将生成临时变量。
参数side,lens[2],rd和*pd都是有名称的,double类型的数据对象,因此可以为其创建引用,而不需要临时变量。然而,edge虽然是变量,类型却不正确,double引用不能指向long;参数7.0和side+10.0的类型都正确,但没有名称,在这些情况下,编译器都将生成一个临时匿名变量,并让ra指向它。这些临时变量只在函数调用期间存在,此后编译器便可以随意将其删除。
8.2.4将引用用于结构
1 #include<iostream>
2 #include<string>
3 using namespace std;
4
5 struct free_throws
6 {
7 string name;
8 int made;
9 int attempts;
10 float percent;
11 };
12
13 void display(const free_throws &ft);
14 void set_pc(free_throws &ft);
15 free_throws & accumulate(free_throws &target, const free_throws &source); //返回类型为free_throws &(引用),所以返回的不是target,而是最初传递给accumulate()的team对象
16
17 int main()
18 {
19 free_throws one = { "If Branch", 13, 14 };
20 free_throws two = { "Andor Knott", 10, 16 };
21 free_throws three = { "Min Max", 7, 9 };
22 free_throws four = { "Whi Loo", 5, 9 };
23 free_throws five = { "Long Long", 6, 14 };
24 free_throws team = { "Throw", 0, 0 };
25 free_throws dup;
26
27 set_pc(one);
28 display(one);
29 accumulate(team, one);
30 display(team);
31 display(accumulate(team, two));
32 accumulate(accumulate(team, three), four);
33 display(team);
34 dup = accumulate(team, five); //如果accumulate()返回一个结构,而不是指向结构的引用,将把整个结构复制到一个临时位置,再将这个拷贝复制给dup。但在返回值为引用时,将直接把team复制到dup,其效率更高
35 cout << "Displaying team: " << endl;
36 display(team);
37 cout << "Displaying dup after assignment: " << endl;
38 display(dup);
39 set_pc(four);
40 accumulate(dup, five) = four; //four赋给dup
41 cout << "Displaying dup after ill-advised assignment:" << endl;
42 display(dup);
43 system("pause");
44 return 0;
45 }
46
47 void display(const free_throws &ft)
48 {
49 cout << "Name: " << ft.name << endl;
50 cout << "Made: " << ft.made << "t";
51 cout << "Attempts: " << ft.attempts << "t";
52 cout << "Percent: " << ft.percent << endl;
53 }
54
55 void set_pc(free_throws &ft)
56 {
57 if (ft.attempts != 0)
58 ft.percent = 100.0f*float(ft.made) / float(ft.attempts);
59 else
60 ft.percent = 0;
61 }
62
63 free_throws & accumulate(free_throws &target, const free_throws &source)
64 {
65 target.attempts += source.attempts;
66 target.made += source.made;
67 set_pc(target);
68 return target;
69 }
返回引用时需要注意的问题:
返回引用时最重要的一点是,应避免返回函数终止时不再存在的内存单元引用。所以应避免编写以下代码:
const free_throws & clone2(free_throws & ft)
{
free_throws newguy;
newguy = ft;
return newguy;
}
该函数返回一个指向临时变量(newguy)的引用,函数运行完毕后它将不再存在。同样,也应避免返回指向临时变量的指针。
为避免这种问题,最简单的方法是,返回一个作为参数传递给函数的引用。上面的accumulate()正是这样做的。
另一种方法是用new来分配新的存储空间。前面见过这样的函数,它使用new为字符串分配内存空间,并返回指向该内存空间的指针。
const free_throws & clone(free_throws & ft)
{
free_throws *pt;
*pt = ft;
return *pt;
}
让指针pt指向该结构,因此*pt就是该结构。上述代码似乎会返回该结构,但函数声明表明,该函数实际上将返回这个结构的引用。这样便可以这样使用该函数:
free_throws & jolly=clone(three);
这使得jolly成为新结构的引用,不过这种存在一个问题:调用clone()隐藏了对new的调用,这使得以后很容易忘记使用delete来释放内存。
8.2.5 将引用用于类对象
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 cout << "Enter a string: ";
15 getline(cin, input);
16 copy = input; //C++中,string可以直接赋值
17 cout << "Your string as entered: " << input << endl;
18 result = version1(input, "***"); //"***"是char *型,但可用于const string &的形参。此处2点需说明:1.string类定义了一种char*到string的转换功能,这使得可以使用C-风格字符串来初始化string对象。
19 //2.前面提到过的如果引用参数是const,编译器在实参的类型不正确,但可以转换为正确的类型的情况下生成临时变量,使用转换后的实参值来初始化它,然后传递一个指向该临时变量的引用。
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 cout << "Resetting original string." << endl;
28 input = copy;
29 result = version3(input, "@@@");
30 cout << "Your string enhanced: " << result << endl;
31 cout << "Your original string: " << input << endl;
32
33 system("pause");
34 return 0;
35 }
36
37 string version1(const string &s1, const string &s2)
38 {
39 string temp;
40 temp = s2 + s1 + s2;
41 return temp; //返回main函数时,tmp不复存在,但tmp的内容被复制到临时存储单元
42 }
43
44 const string & version2(string &s1, const string &s2) //改变了s1的内容
45 {
46 s1 = s2 + s1 + s2;
47 return s1;
48 }
49
50 const string & version3(string &s1, const string &s2)
51 {
52 string temp;
53 temp = s2 + s1 + s2;
54 return temp; //返回main函数时,tmp内存已被释放,程序不能引用已经释放的内存
55 }
此时该程序已经崩溃。
8.2.6 对象、继承和引用
正如第6章介绍的,ofstream对象(文件用于处理输出的类)可以使用ostream类(控制台用于处理输出的类)的方法,这使得文件输入/输出的格式与控制台输入/输出相同。ostream是基类,ofstream是派生类,派生类继承了基类的方法,这意味着ofstream对象可以使用基类的特性。
继承的另一个特征是,基类引用可以指向派生类对象,而无需进行强制类型转换。这种特征的一个实际结果是,可以定义一个接受基类引用作为参数的函数,调用该函数时,可以将基类对象作为参数,也可以将派生类对象作为参数。
1 #include <iostream>
2 #include <fstream>
3 #include <cstdlib> //支持exit()函数
4 using namespace std;
5
6 void file_it(ostream &os, double fo, const double fe[], int n);
7 const int LIMIT = 5;
8 int main()
9 {
10 ofstream fout;
11 const char *fn = "ex-data.txt";
12 fout.open(fn); //若程序运行前ex-data.txt不存在,open()将新建一个名为ex-data.txt的文件;若程序运行前存在,open()将首先截断该文件,即将长度截短到0,丢其原来所有内容,然后将新的输出加入到该文件中
13 if (!fout.is_open()) //打开文件失败
14 {
15 cout << "Can't open " << fn << ".Bye.n";
16 exit(EXIT_FAILURE); //异常退出
17 }
18 double objective;
19 cout << "Enter the focal length of your "
20 "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 << "Enter #" << i + 1 << ": ";
27 cin >> eps[i];
28 }
29 file_it(fout, objective, eps, LIMIT); //将数据写入文件
30 file_it(cout, objective, eps, LIMIT); //将同样的信息以相同的格式显示到屏幕上
31 cout << "Donen";
32 return 0;
33 }
34
35 void file_it(ostream &os, double fo, const double fe[], int n)
36 {
37 ios_base::fmtflags initial; //ios_base::fmtflags是储存这种信息所需的数据类型名称,initial存储调用file_it()之前的格式化设置
38 //setf()方法能够设置各种格式化状态
39 initial = os.setf(ios_base::fixed); //set(ios_base::fixed)将对象置于使用定点表示法的模式
40 os.precision(0); //指定显示多少位小数(假定对象处于定点模式下)
41 os << "Focal length of objective: " << fo << " mmn";
42 os.setf(ios::showpoint); //将对象置于显示小数点的模式,即使小数部分为零
43 os.precision(1);
44 os.width(12);
45 os << "f.l. eyepiece";
46 os.width(12);
47 os << "magnification" << endl;
48 for (int i = 0; i < n; i++)
49 {
50 os.width(12);
51 os << fe[i];
52 os.width(15);
53 os << int(fo / fe[i] + 0.5) << endl;
54 }
55 os.setf(initial); //使用initial作为参数来调用setf(),将返回调用file_it()之前有效的所有格式化设置
56 }
以下是一些常见的控制函数的:
dec 置基数为10 相当于"%d"
hex 置基数为16 相当于"%X"
oct 置基数为8 相当于"%o"
setfill(c) 设填充字符为c
setprecision(n) 设显示小数精度为n位
setw(n) 设域宽为n个字符
setioflags(ios::fixed) 固定的浮点显示
setioflags(ios::scientific) 指数表示
setiosflags(ios::left) 左对齐
setiosflags(ios::right) 右对齐
setiosflags(ios::skipws 忽略前导空白
setiosflags(ios::uppercase) 16进制数大写输出
setiosflags(ios::lowercase) 16进制小写输出
setiosflags(ios::showpoint) 强制显示小数点
setiosflags(ios::showpos) 强制显示符号
8.3 默认参数
默认参数指的是当函数调用中省略了实参时自动使用的一个值。
注意:只有函数原型指定了默认值,函数定义与没有默认参数时完全相同。
1 #include <iostream>
2 using namespace std;
3 const int ArzSize = 80;
4 char *left(const char *str, int n = 1);
5
6 int main()
7 {
8 char sample[ArzSize];
9 cout << "Enter a string: n";
10 cin.get(sample, ArzSize);
11 char *ps = left(sample, 4);
12 cout << ps << endl; //如果给cout提供一个指针,它将打印地址,但如果这个指针类型是char *,则cout将显示指向的字符串;若要显示地址,则应cout<<(int *)ps;如果couot<<*ps,则只会显示字符串的第一个字符
13 delete[]ps;
14 ps = left(sample);
15 cout << ps << endl;
16 delete[]ps;
17 return 0;
18
19 }
20
21 char *left(const char *str, int n) //内部使用了new没有delete,所以main函数调用此方法后一定要记得delete
22 {
23 if (n < 0)
24 n = 0;
25 char *p = new char[n + 1]; //new和delete成对出现
26 int i;
27 for (i = 0; i < n && str[i]; i++)
28 {
29 p[i] = str[i];
30 }
31 while(i<=n)
32 p[i++] = '