2.6 C++析构函数

创建对象系统时系统会自动调用构造函数进行初始化工作,同样,销毁对象时系统也会自动调用一个函数来进行清理工作(例如回收创建对象时消耗的各种资源),这个函数被称为析构函数。
析构函数(Destructor)也是一种特殊的成员函数,没有返回值,不需要用户调用,而是在销毁对象时自动执行。与构造函数不同的是,析构函数的名字是在类名前面加一个”~“符号。

注意:析构函数没有参数,不能被重载,因此一个类只能有一个析构函数。如果用户没有定义,那么编译器会自动生成。
例如:

%e3%82%ad%e3%83%a3%e3%83%97%e3%83%81%e3%83%a319

可以看出,析构函数在main函数运行结束前被执行,并且调用顺序和构造函数正好相反,为了方便记忆,我们可以将之理解为一个栈,先入后出。
析构函数在对象被销毁前执行;要知道析构函数什么时候被调用,就要先知道对象什么时候被销毁。

对象可以认为是通过类这种数据类型定义的变量,它的很多特性和普通变量是一样的,例如作用域、生命周期等。由此可以推断,对象这种变量的销毁时机和普通变量是一样的。

总结起来,有下面几种情况:
1) 如果在一个函数中定义了一个对象(auto 局部变量),当这个函数运行结束时,对象就会被销毁,在对象被销毁前自动执行析构函数。

2) static 局部对象在函数调用结束时并不销毁,因此也不调用析构函数,只有在程序结束时(如 main 函数结束或调用 exit 函数)才调用 static 局部对象的析构函数。

3) 如果定义了一个全局对象,也只有在程序结束时才会调用该全局对象的析构函数。

4) 如果用 new 运算符动态地建立了一个对象,当用 delete 运算符释放该对象时,先调用该对象的析构函数。
注意:析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以分配给新对象使用。

2.5 C++构造函数的参数初始化表

构造函数是一种特殊的成员函数,在创建对象时自动执行,主要用来进行初始化工作,例如对private属性的成员变量赋值。
对成员变量的初始化,除了在构造函数的函数体中-赋值,还可以采用参数初始化表。
例如:

%e3%82%ad%e3%83%a3%e3%83%97%e3%83%81%e3%83%a318

本例所示,在定义带参构造函数 Student(char *name1, int age1, float score1) 时,不再是在函数体中对成员变量一一赋值,其函数体为空。在函数首部与函数体之间增添了一个冒并加上 name(name1), age(age1), score(score1) 语句,这个语句的意思相当于函数体内部的 name = name1; age = age1; score = score1; 语句。
这样做对于三个成员变量的类来说可能看不出什么优势,但是当成员变量非常多的时候,通过参数初始化列表进行赋值的优势就显现出来了,如此写法简洁明了。

参数初始化表还有一个很重要的作用,那就是为 const 成员变量初始化。例如:
1. class Array{
2. public:
3. Array(){
4. length = 0; //compile error
5. num = NULL;
6. };
7. private:
8. const int length;
9. int * num;
10. };
在本例中Array类声明了两个成员变量,length 和 num 指针,需要注意的是 length 加了 const 关键字修饰。此时默认构造函数再为 length 赋值为 0,这是无法通过编译的。

初始化const成员变量的唯一方法只有利用参数初始化表。例如:
1. class Array{
2. public:
3. Array(): length(0){
4. num = NULL;
5. };
6. private:
7. const int length;
8. int * num;
9. };
需要注意的是:参数初始化顺序与初始化表列出的变量的顺序无关,参数初始化顺序只与成员变量在类中声明的顺序有关。

2.4 C++构造函数

当创建一个对象时,往往需要做一些初始化工作,例如对数据成员赋值等。为了解决这个问题,C++提供了构造函数。

构造函数(Constructor)是一种特殊的成员函数,它的名字和类名相同,没有返回值,不需要用户调用(用户也不能调用),而是在创建对象时自动执行。构造函数的作用是在创建对象时进行初始化工作,最常见的就是对成员变量赋值。
例如:

%e3%82%ad%e3%83%a3%e3%83%97%e3%83%81%e3%83%a317

在类中我们定义了一个构造函数Student(),它的作用是给3个private属性 的成员变量赋值。在mian函数中,我们根据构造函数创建了一个对象 stu;因为构造函数有参数,所以创建对象时要相应地传入实参,形式类似于函数调用。

注意:一旦在类中定义了构造函数,那么创建对象时一定会被执行;如果构造函数有参数,创建对象时就要传参。
另外,构造函数主要用来进行初始化,没有返回值(有返回值没有任何意义),这就意味着:
• 不管是声明还是定义,函数名前面都不能出现返回值类型,即使是 void 也不允许;
• 函数体中不能有 return 语句。

默认构造函数

如果用户自己没有定义构造函数,那么编译器会自动生成一个默认的构造函数,只是这个构造函数的函数体是空的,也没有参数,不执行任何操作。比如上面的 Student 类,默认生成的构造函数如下:
Student(){}
一个类,必须有构造函数,要么用户自己定义,要么编译器自动生成。一旦用户自己定义了构造函数,不管它是 public 属性的,还是 private、protected 属性的,编译器都不再自动生成。上面的 Student 类,只有一个构造函数,就是我们自己定义的。

构造函数的重载

和普通的函数一样,构造函数是允许重载的。一个类可以提供多个构造函数。让用户在创建对象时进行选择,编译器会根据创建对象时传递的参数来确定调用哪一个构造函数。也就是说:
• 只有一个构造函数会被执行;
• 创建对象时提供的参数必须和其中的一个构造函数匹配,否则编译错误。
例如:
1. #include
2. using namespace std;
3.
4. class Student{
5. private:
6. char *name;
7. int age;
8. float score;
9.
10. public:
11. //声明构造函数
12. Student();
13. Student(char *, int, float);
14. //声明普通成员函数
15. void setname(char *);
16. void setage(int);
17. void setscore(float);
18. void say();
19. };
20.
21. //定义构造函数
22. Student::Student(){}
23. Student::Student(char *name1, int age1, float score1){
24. name = name1;
25. age = age1;
26. score = score1;
27. }
28. //定义普通成员函数
29. void Student::setname(char *name1){
30. name = name1;
31. }
32. void Student::setage(int age1){
33. age = age1;
34. }
35. void Student::setscore(float score1){
36. score = score1;
37. }
38. void Student::say(){
39. cout<<name<<“的年龄是 “<<age<<“,成绩是 “<<score<<endl;
40. }
41.
42. int main(){
43. //创建对象时初始化成员变量
44. Student stu1(“小明”, 15, 90.5f);
45. stu1.say();
46.
47. //调用成员函数来初始化成员变量的值
48. Student stu2;
49. stu2.setname(“李磊”);
50. stu2.setage(16);
51. stu2.setscore(95);
52. stu2.say();
53.
54. return 0;
55. }
运行结果:
小明的年龄是 15,成绩是 90.5
李磊的年龄是 16,成绩是 95
类中定义了两个构造函数,一个带参数一个不带参数,它们是重载关系。当根据不带参数的构造函数创建对象时,不需要传参,成员变量不会被初始化,所以要调用成员函数来设置它们的值。

2.3 C++类成员的访问权限

C++通过public,protected,private三个关键字来控制成员变量和成员函数的访问权限,它们分别表示公有的、受保护的、私有的,被称为成员访问限定符。所谓访问权限,就是你能不能使用该类中的成员。

Java、C# 程序员注意,C++ 中的 public、private、protected 只能修饰类的成员,不能修饰类,C++中的类没有共有私有之分。

访问权限需要分两部分说明:类内和类外。
• 在类内部,无论成员变量或成员函数被声明为 public、protected 还是 private,都是可以互相访问的,无访问权限限制。
• 在类外部,通过对象无法访问 private、protected 属性的成员变量和成员函数,而仅可以访问 public 属性的成员变量和成员函数。
定义一个 Student 类来说明成员的访问权限:

%e3%82%ad%e3%83%a3%e3%83%97%e3%83%81%e3%83%a316
类中的成员变量 name、age 和 score 被设置成 private 属性,在类外部的代码都不能访问,即使通过该类创建的对象也不行。也就是说,私有成员变量和成员函数只能在类内部使用,在类外都是无效的。

成员函数 setname()、setage() 和 setscore() 被设置为 public 属性,是公有的,可以通过对象访问。

因为三个成员变量都是私有的,不能通过对象直接访问,所以必须借助三个 public 属性的成员函数来修改它们的值。
下面的代码是错误的:
1. Student stu;
2. //name、age、score 是私有成员变量,只能在类内部使用
3. stu.name = “小明”;
4. stu.age = 15;
5. stu.score = 92.5f;
6. stu.say();
private 成员的作用在于更好地隐藏类的内部实现,该向外暴露的接口都声明为 public,不希望外部知道、或者只在类内部使用的、或者对外部没有影响的成员,都建议声明为 private,这样的代码更加符合软件设计规范。

另外还有一个关键字 protected,声明为 protected 属性的成员变量或成员函数,在类外也是不可以访问的,但是其派生类内部可以访问,我们将在后续章节介绍,在这里你只需要知道 protected 在类外无法访问即可。

两点说明:
1) 声明为 private 的成员和声明为 public 的成员的次序任意,既可以先出现 private 部分,也可以先出现 public 部分。如果既不写 private 也不写 public,就默认为 private。

2) 在一个类体中,private 和 public 可以分别出现多次。每个部分的有效范围到出现另一个访问限定符或类体结束时(最后一个右花括号)为止。但是为了使程序清晰,应该养成这样的习惯,使每一种成员访问限定符在类定义体中只出现一次。

2.2 C++类的成员变量和成员函数

类是一种数据类型,它类似于普通的数据类型,但是又有别于普通的数据类型。类这种数据类型是一个包含成员变量和成员函数的一个集合。
类的成员变量和普通变量一样,也有数据类型和名称,占用固定长度的内存空间。但是,在定义类的时候不能对成员变量赋值,因为类只是一种数据类型,本身不占用内存空间,而变量的值则需要内存来存储。

类的成员函数也和普通函数一样,都有返回值和参数列表,它与一般函数的区别是:成员函数是一个类的成员,出现在类体中,它的作用范围由类来决定;而普通函数是独立的,作用范围是全局的,或位于某个命名空间内。

上节我们在最后的完整示例中给出了 Student 类的定义,如下所示:
1. class Student{
2. public: //类包含的变量
3. char *name;
4. int age;
5. float score;
6.
7. public: //类包含的函数
8. void say(){
9. printf(“%s的年龄是 %d,成绩是 %f\n”, name, age, score);
10. }
11. };
上面的代码在类体中定义了成员函数。你也可以只在类体中声明函数,而将函数定义放在类体外面,如下图所示:
1. class Student{
2. public:
3. char *name;
4. int age;
5. float score;
6.
7. public:
8. void say(); //函数声明
9. };
10.
11. //函数定义
12. void Student::say(){
13. printf(“%s的年龄是 %d,成绩是 %f\n”, name, age, score);
14. }
在类体中直接定义函数时,不需要在函数名前面加上类名,因为函数属于哪一个类是不言而喻的。
但当成员函数定义在类外时,就必须在函数名前面加上类名予以限定。::被称为域解析符(也称作用域运算符或作用域限定符),用来连接类名和函数名,指明当前函数属于哪一个类。
如果在域解析符“::”的前面没有类名,或者函数名前面既无类名又无域解析符“::”,如:
1. //无类名
2. ::say( ){
3. //TODO
4. }
5. //无类名也无域解析符
6. say( ){
7. //TODO
8. }
则表示 say() 函数不属于任何类,这个函数不是成员函数,而是全局函数,即非成员函数的一般普通函数。
成员函数必须先在类体中作原型声明,然后在类外定义,也就是说类体的位置应在函数定义之前,否则编译时会出错。
虽然成员函数在类的外部定义,但在调用时会根据在类中声明的函数原型找到函数的定义(函数代码),从而执行该函数。

Inline成员函数

在类体中和类体外定义成员函数是有区别的:在类体中定义的成员函数为内联(inline)函数,在类体外定义的不是。
内联函数一般不是我们所期望的,它会将函数调用处用函数体替代,所以我建议在类体内部对成员函数作声明,而在类体外部进行定义,这是一种良好的编程习惯。
当然,如果你的函数比较短小,希望定义为内联函数,那也没有什么不妥的。

如果你既希望将函数定义在类体外部,又希望它是内联函数,那么可以在声明函数时加 inline 关键字如下所示:
1. class Student{
2. public:
3. char *name;
4. int age;
5. float score;
6.
7. public:
8. inline void say(); //声明为内联函数
9. };
10.
11. //函数定义
12. void Student::say(){
13. printf(“%s的年龄是 %d,成绩是 %f\n”, name, age, score);
14. }
这样,say() 就会变成内联函数。
值得注意的是,如果在类体外定义 inline 函数,则必须将类定义和成员函数的定义都放在同一个头文件中(或者写在同一个源文件中),否则编译时无法进行嵌入(将函数代码的嵌入到函数调用出)。这样做虽然提高了程序的执行效率,但从软件工程质量的角度来看,这样做并不是好的办法,因此实际开发中较少在类中使用内联函数。
C++提出内联函数的主要用意是:用内联函数取代带参宏定义(函数传参比宏更加方便易用),而不是提高程序运行效率,因为与执行函数花费的时间相比,调用函数花费的时间往往微乎其微。

2.1 C++类的声明和对象的创建

类是创建对象的模板,一个类可以创建多个对象,每个对象都是类类型的一个变量;创建对象的过程也叫类的实例化。
每个对象都是类的一个具体事例(Instance),拥有类的成员变量和成员函数。
与结构体一样,类只是一种复杂数据类型的声明,不占用内存空间。而对象是类这种数据类型的一个变量,占用内存空间。
类的声明
类是用户自定义的类型,如果程序中要用到类,必须先进行声明,或者使用已存在的类(别人写好的类、标准库中的类等),C++语法本身并不提供现成的类的名称、结构和内容。
一个简单的类的定义:

%e3%82%ad%e3%83%a3%e3%83%97%e3%83%81%e3%83%a314
该例创建了一个student类,它包含了3个成员变量和1个成员函数。
Class是C++中的关键字,用于声明一个类;紧接class关键字之后的是我们自定义的类名 Student;由{ }包围的是类体。声明类时不能对成员变量进行初始化,只有在创建对象以后才能赋值。
类可以理解为一种新的数据类型,该数据类型的名称是 Student。与 char、int、float 等基本数据类型不同的是,Student 是一种复杂数据类型,可以包含基本类型,而且多了很多基本类型中没有的特性。
需要注意的是:在类声明的最后有一个分号(;),它是类声明的一部分,表示类声明结束了,不能省略。

创建对象

声明了Student数据类型之后,就可以用它来定义变量了,如:
1. Student LiLei; //创建对象
该语句声明了一个名字为 LiLei、数据类型为 Student 的变量。这和:
1. int a; //定义整形变量
语句定义了一个整型变量表达的意思是类似的。而 LiLei 这个变量我们称之为 Student 类的对象。

在定义类的对象时,class 关键字可要可不要。但出于习惯我们通常都会省略掉class关键字,例如:
1. class Student LiLei; //正确
2. Student LiLei; //同样正确
创建类的对象时,除了能定义单个变量以外,还可以定义一个数组或者指针。例如:
1. Student all_student[100];
2. Student *pointer;
第一条语句定义了一个 all_student 数组,该数据拥有100个元素,每个元素都是 Student 类型。第二条语句定义了一个 Student 类型的指针 pointer,该指针可以指向 Student 类型的变量(对象),用法和普通指针一样。

声明类的同时创建对象

和结构体(struct)类似,你可以先声明类,然后再创建对象,也可以在声明类的同时创建对象。
1. class Student{
2. //成员变量
3. char *name;
4. int age;
5. float score;
6.
7. //成员函数
8. void say(){
9. printf(“%s的年龄是 %d,成绩是 %f\n”, name, age, score);
10. }
11. }stu1, stu2;

这个时候你也可以省略类名,直接创建对象。如下所示:
1. class{
2. //成员变量
3. char *name;
4. int age;
5. float score;
6.
7. //成员函数
8. void say(){
9. printf(“%s的年龄是 %d,成绩是 %f\n”, name, age, score);
10. }
11. }stu1, stu2;

直接定义对象,在C++中是合法的、允许的,但却很少用,也不提倡用。
实例:

%e3%82%ad%e3%83%a3%e3%83%97%e3%83%81%e3%83%a315

public是C++中的关键字,用来修饰成员变量和成员函数,表示它们是公有的。只有public后面的成员变量和成员函数才能被创建的对象访问。
Main函数中首先创建了一个对象 stu,然后又定义了一个 Student 类型的指针变量。可以发现,和结构体(struct)类似,一个对象通过成员选择符”.“来访问成员变量和成员函数,而指针变量通过指针操作符”->“来访问成员。
注意:对象指针指向的是一个具体的对象,而不是类。下面的写法是错误的:
1. Student *pt;
2. pt = &Student;

1.11 C++引用(Peference)

引用(Reference)是C++相对C语言的又一个扩充。引用类似于指针,只是在声明的时候用&取代*。引用可以看做是被引用对象的一个别名,在声明引用时,必须同时对其进行初始化。引用的声明方法如下:
类型标识符 &引用名 = 被引用对象
例如:

%e3%82%ad%e3%83%a3%e3%83%97%e3%83%81%e3%83%a39
从运行结果可以看出,变量a和变量 b 都是指向同一地址的,也即变量 b 是变量 a 的另一个名字,也可以理解为 0018FDB4 空间拥有两个名字:a和b。由于引用和原始变量都是指向同一地址的,因此通过引用也可以修改原始变量中所存储的变量值。
例如:

%e3%82%ad%e3%83%a3%e3%83%97%e3%83%81%e3%83%a310
如果我们不希望通过引用来改变原始变量的值时,我们可以按照如下的方式声明引用:
const 类型标识符 & 引用名 = 被引用的变量名
这种引用方式成为常引用。

%e3%82%ad%e3%83%a3%e3%83%97%e3%83%81%e3%83%a311
我们声明c为a的常引用,之后通过c来修改a变量的值,结果编译报错。虽然常引用无法修改原始变量的值,但是我们仍然可以通过原始变量自身来修改原始变量的值。
通过引用我们可以修改原始变量的值,引用的这一特性使得它用于函数传递参数或函数返回值时非常有用。

函数引用参数

如果我们在声明或定义函数的时候将函数的形参指定为引用,则在调用该函数时会将实参直接传递给形参,而不是将实参的拷贝传递给形参。如此一来,如果在函数体中修改了该参数,则实参的值也会被修改。这跟函数的普通传值调用还是有区别的。

%e3%82%ad%e3%83%a3%e3%83%97%e3%83%81%e3%83%a312
在本例中我们将swap函数的形参声明为引用,在调用swap函数的时候程序是将变量a和b
直接传递给形参的,其中a是a的别名,b是b的别名,在swap函数体中交换变量a和变量b的值,也就相当于直接交换变量a和变量b的值了,因此程序最后a=20,b=10。

函数引用返回值

在C++中非void型函数需要返回一个返回值,类似函数形参,我们同样可以将函数的返回值声明为引用。普通的函数返回值是通过传值返回,即将关键字return后面紧接的表达式运算结果或变量拷贝到一个临时存储空间中,然后函数调用者从临时存储空间中取到函数返回值。

%e3%82%ad%e3%83%a3%e3%83%97%e3%83%81%e3%83%a313
Valplus1函数采用的是普通的传值返回,也即将变量a的结果加上5之后,将结果拷贝到一个临时存储空间,然后再从临时存储空间拷贝给num2(15)变量。 当我们将函数返回值声明为引用的形式时,虽然运行结果相同, valplus2函数在将a变量加上5之后,其运算结果是直接拷贝给num3(20)的,中间没有经过拷贝给临时空间,再从临时存储空间中拷贝出来的这么一个过程。这就是普通的传值返回和引用返回的区别。

此外,我们还需要注意一个小问题。如果我们将valplus2函数定义如下所示的形式,那么这段程序就会产生一个问题,变量b的作用域仅在这个valplus2函数体内部,当函数调用完成,b变量就会被销毁。而此时我们若将b变量的值通过引用返回拷贝给变量num3的时候,有可能会出现在拷贝之前b变量已经被销毁,从而导致num3变量获取不到返回值。虽然这种情况在一些编译器中并没有发生,但是我们在设计程序的时候也是应该尽量避免这一点的。

以上的例子中,我们就是为了避免这一点才采用的引用传递参数。普通的传值返回则不存在这样的问题,因为编译器会将返回值拷贝到临时存储空间后再去销毁b变量的。

[例]一个可能获取不到返回值的例子:
1. int & valplus2(int a){
2. int b = a+5;
3. return b;
4. }

1.10 C++函数的默认参数

在C++中,定义函数时可以给参数指定一个默认的初始值。调用函数时,可以省略有默认值的参数。也就是说,如果用户指定了参数的值,那么就使用用户指定的值
指定了默认值的参数,称为默认参数。
例如:

%e3%82%ad%e3%83%a3%e3%83%97%e3%83%81%e3%83%a38
本例定义了一个带有默认参数的函数 func(),并在 main() 函数中进行了不同形式的调用。为参数指定默认值非常简单,直接在形参列表中赋值即可,与定义普通变量的形式类似。
默认参数的值除了使用字面量指定,也可以使用表达式指定,例如:
1. float d = 10.8;
2. void func(int n, float b=d+2.9, char c=’@’){
3. cout<<n<<“, “<<b<<“, “<<c<<endl;
4. }

C++规定,默认参数只能放在形参列表的最后,而且一旦为某个参数指定了默认值,那么它后面的所有参数都必须有默认值。实参和形参的传值是从左到右依次匹配的,默认参数的连续性是保证正确传参的前提。
下面的写法是正确的:
1. void func(int a, int b=10, int c=20){ }
2. void func(int a, int b, int c=20){ }
但这样写不可以:
1. void func(int a, int b=10, int c=20, int d){ }
2. void func(int a, int b=10, int c, int d=20){ }

除了函数定义,你也可以在函数声明中指定默认参数。不过默认参数只能指定一次,在声明中指定了就不能在定义中指定,反过来也一样。请看下面的例子:
1. #include
2. using namespace std;
3.
4. void func(int a, int b=10, int c=20);
5.
6. int main(){
7. func(20, 30);
8. return 0;
9. }
10.
11. //不能再指定默认参数
12. void func(int a, int b, int c){
13. cout<<a<<“, “<<b<<“, “<<c<<endl;
14. }
运行结果:
20, 30, 20

默认参数和重载
默认参数和重载不能同时出现:使用了默认参数就不能使用重载,使用了重载也不能使用默认参数,它们是相互冲突的。因为当调用函数时如果少写一个参数,编译器就无法判定是利用函数重载还是利用默认参数,会出现二义性,无法执行。

歴史は塗りつぶせるか

60年前、都内に住む国民学校5年生が日記にこう書いた。「本日は連合軍進駐の日とて、米機すこぶる低空にていういうと飛び行く。くやしいが如何(いかん)とも出来ぬ。ただ勉強するのみ」
  60年前,住在东京都内国民学校的一名5年级小学生,在日记里这样写到”今天是联合国军进驻的日子,美军的飞机在非常低的天空嗡嗡盘旋。无论怎样懊恼都无济于事了,现在(我们)能做的就是学习。
  やがて占領軍の命令で、教科書の軍国主義的な個所を墨で塗らされ、絶対と信じていた天皇中心の日本史が否定された。少年は思い知らされた、戦争の結果次第で歴史は書き換えられるのだと。
  不久接到占领军的命令,日本教科书中原本关于军国主义的地方全部被修改,对天皇绝对信仰的日本史也遭到全盘否定。少年们体会到历史是会因为战争的结果而被改写的。
  後にアメリカ歴史学会の会長を日本人として初めて務めた入江昭ハーバード大教授(71)である。このほど出した回想録「歴史を学ぶということ」(講談社現代新書)で、教科書の墨塗りが自分の歴史家としての出発点だったとふりかえっている。
  后来作为日本人首次担任美国历史学会会长的是入江昭哈佛大教授(71岁)。他在最近出版的回忆录《学习历史》(讲谈社现代新书)中,以自己作为历史学家的出发点回顾了重写教科书这件事情。
  それは、歴史は勝者が書くのだという単純な論ではない。「国家権力や政治的思惑によって歴史が書き換えられうるからこそ、歴史家はあくまでも自由な意思と努力で史実を追求しなければならない」という決意だ。
  这并不能简单地归结为历史是由胜利者撰写的。入江大教授认为「他们都是根据国家权力和政治的考虑而对历史进行修改的。不用说历史学家也必须按照自由的意思和努力去追求历史的真实」。
  入江さんの仕事の特色は、一国だけの狭い視点ではなく、国家を超える経済や文化の動きを視野に入れて、国際社会の全体像を描き出すことだ。「学問はナショナリズムから自由にならねばならない」という思いに支えられている。
  入江教授工作的特色,就是并非局限于一个国家狭隘的视野,而是把超越国家经济、文化的动向纳入其视野,对国际社会做出一个全体的描绘。这是由「学问必须从民族主义出发自由地发展」这个观点所支撑的。
  「歴史とは現在と過去との対話だ」(英国の史家E?H?カー)と言われるが、現在の問題意識で歴史はいかようにでも解釈できるということでは困る。教科書に墨を塗っても、歴史は塗りつぶせない。肝要なのは、塗りつぶした過去との冷静な対話ではないか。軍国少年から出発した入江さんの歩みがそれを示している。
  英国的史学家E?H?カー认为”历史是现在和过去的对话”,但现在的问题意识中历史却有多种解释,这一点又令他为之困惑。教科书可以重新撰写,但历史却是无法篡改的。重要的是,是否能够与被篡改的历史进行冷静地对话呢?从军国主义时代的少年到现在(成为教授的)入江先生,其经历正说明了这一切。

1.9 C++函数重载

实际开发中,有时我们要实现的是同一类的功能,只是有些细节不同。例如希望从3个数中找出其中的最大者,而每次求最大数时数据的类型不同,可能是3个整数、3个双精度数或3个长整数。在C语言中,程序员往往需要分别设计出3个不同名的函数,其函数原型与下面类似:
1. int max1(int a, int b, int c); //求3个整数中的最大者
2. double max2(double a, double b, double c); //求3个双精度数中最大者
3. long max3(long a, long b, long c); //求3个长整数中的最大者

但在C++中,这完全没有必要。C++允许多个函数拥有相同的名字,只要它们的参数列表不同就可以。这就是函数的重载(Function Overloading)。借助重载,一个函数名可以有多种用途。

参数列表又叫参数签名,包括参数的类型,参数的个数和参数的顺序,只要有一个不同就叫做参数列表不同。
例如:

%e3%82%ad%e3%83%a3%e3%83%97%e3%83%81%e3%83%a37
通过上例可以发现,重载就是在一个作用范围内(同一个类、同一个命名空间等),有多个名称相同但参数不同的函数。重载的结果,可以让一个程序段尽量减少代码和方法的种类。

在使用重载函数时,同名函数的功能应当相同或相近,不要用同一函数名去实现完全不相干的功能,虽然程序也能运行,但可读性不好,使人莫名其妙。

说明:参数列表不同包括个数不同、类型不同和顺序不同,仅仅参数变量名称不同是不可以的。

函数的重载的规则:
• 函数名称必须相同。
• 参数列表必须不同(个数不同、或类型不同、参数排列顺序不同等)。
• 函数的返回类型可以相同也可以不相同。
• 仅仅返回类型不同不足以成为函数的重载。
函数重载的实现
C++ 代码在编译时会根据参数列表对函数进行重命名,例如,int max(int, int, int) 会被重命名为 _max_int_int_int,double max(double, double, double) 会被重命名为 _max_double_double_double,所以在本质上来说它们还是不同的函数。
不同的编译器有不同的重命名方式,这里仅仅举例说明。
当发生函数调用时,编译器会根据参数列表去逐个匹配,以选择对应的函数,如果匹配失败,则编译器报错。这叫做重载分辨。