[C++ Primer Plus] 第8章、函数探幽——(一)程序清单
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 << "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; //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 << " mm\n";
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++] = '\0';
33 return p;
34 }

8.4 函数重载
默认参数让你能够使用不同数目的参数调用同一个函数。而函数多态(函数重载)让你能够使用多个同名的函数,“多态”指的是有多种形式,因此函数多态允许函数可以有多种形式。
C++允许定义名称相同的函数,条件是它们的特征标不同。函数的参数列表(也称为函数特征标):参数数目、参数类型、排列顺序。如果参数数目和/或参数类型不同,则特征标也不同。
1 #include <iostream>
2 using namespace std;
3
4 unsigned long left(unsigned long num, unsigned ct);
5 char * left(const char *str, int n = 1);
6
7 int main()
8 {
9 char *trip = "Hawaii!!";
10 unsigned long n = 12345678;
11 int i;
12 char *temp;
13
14 for (i = 1; i < 10;i++)
15 {
16 cout << left(n, i) << endl;
17 temp = left(trip, i);
18 cout << temp << endl;
19 delete []temp; //此处delete
20 }
21 retuen 0;22 system("pause"); //这里写反了,stystem应该在return 0前面
23 }
24
25 unsigned long left(unsigned long num, unsigned ct)
26 {
27 unsigned digits = 1;
28 unsigned long n = num;
29
30 if (ct == 0 || num == 0)
31 return 0;
32 while (n /= 10)
33 {
34 digits++;
35 }
36 if (digits > ct)
37 {
38 ct = digits - ct;
39 while (ct--)
40 num /= 10;
41 return num;
42 }
43 else
44 return num;
45 }
46
47 char * left(const char *str, int n) //内部使用了new没有delete,所以main调用后一定要记得delete
48 {
49 if (n < 0)
50 n = 0;
51 char *p = new char[n + 1]; //new和delete成对出现
52 int i;
53 for (i = 0; i < n && str[i];i++)
54 {
55 p[i] = str[i];
56 }
57 while (i <= n)
58 p[i++] = '\0';
59 return p;
60 }

8.5 函数模板
函数模板是通用的函数描述,也就是说,它们使用泛型来定义函数,其中的泛型可用具体的类型(如int或double)替换。
如果需要多个将同一种算法用于不同类型的函数,请使用模板。
1 #include <iostream>
2 using namespace std;
3
4 template<typename T> //函数模板
5 void Swap(T &a, T &b); //不能用swap函数名,用户自己定义的swap()和STL库定义的函数重装冲突
6
7 int main()
8 {
9 int i = 10, j = 20;
10 cout << "i,j= " << i << "," << j << endl;
11 Swap(i, j);
12 cout << "Now i,j= " << i << "," << j << endl;
13
14 double x = 24.5, y = 81.7;
15 cout << "x,y= " << x << "," << y << endl;
16 Swap(x, y);
17 cout << "Now x,y= " << x << "," << y << endl;
18
19 system("pause");
20 return 0;
21 }
22
23 template<typename T> //要记得写在这里
24 void Swap(T &a, T &b)
25 {
26 T temp;
27 temp = a;
28 a = b;
29 b = temp;
30 }

8.5.1 重载的模板
1 #include <iostream>
2 using namespace std;
3
4 template<typename T> //函数模板
5 void Swap(T &a, T &b); //不能用swap函数名,用户自己定义的swap()和STL库定义的函数重装冲突
6
7 template<class T> //模板的另一种表示方式
8 void Swap(T *a, T *b, int n);
9
10 void show(int a[]);
11 const int Lim = 8;
12
13 int main()
14 {
15 int i = 10, j = 20;
16 cout << "i,j= " << i << "," << j << endl;
17 Swap(i, j);
18 cout << "Now i,j= " << i << "," << j << endl;
19
20 int d1[Lim] = { 0,7,0,4,1,7,7,6 };
21 int d2[Lim] = { 0,7,2,0,1,9,6,9 };
22 cout << "Original arrays: " << endl;
23 show(d1);
24 show(d2);
25 Swap(d1, d2, Lim);
26 cout << "Swap arrays: " << endl;
27 show(d1);
28 show(d2);
29
30 system("pause");
31 return 0;
32 }
33
34 template<class T>
35 void Swap(T &a, T &b)
36 {
37 T temp;
38 temp = a;
39 a = b;
40 b = temp;
41 }
42
43 template<typename T>
44 void Swap(T *a, T *b, int n)
45 {
46 T temp;
47 for (int i = 0; i < n; i++)
48 {
49 temp = a[i];
50 a[i] = b[i];
51 b[i] = temp;
52 }
53 }
54
55 void show(int a[])
56 {
57 cout << a[0] << a[1] << "/";
58 cout << a[2] << a[3] << "/";
59 for (int i = 4; i < Lim; i++)
60 {
61 cout << a[i];
62 }
63 cout << endl;
64 }

8.5.3 显式具体化
需要显式具体化的情况:
假设定义了如下结构:
struct job
{
char name[40];
double salary;
int floor;
};
假设希望能交换两个这种结构的内容,原来的模板使用下面的代码来完成交换:
temp = a;
a = b;
b = temp;
但是如果只想交换salary和floor成员,而不想交换name成员,则需要使用不同的代码,但Swap()的参数将保持不变,因此无法使用模板重载来提供其他的代码,这样就有了显示具体化。
可以提供一个具体化函数定义,称为显式具体化,其中包含所需代码, 当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不在寻找模板。
- 对于给定的函数名,可以有非模板函数、模板函数和显式具体化模板函数以及它们的重载版本。
- 显式具体化的原型和定义应以template<>打头,并通过名称来指出类型。
- 具体化优先于常规模板,而非模板函数优先于具体化和常规模板。
1 #include <iostream>
2 using namespace std;
3
4 template<typename T>
5 void Swap(T &a, T &b);
6
7 struct job
8 {
9 char name[40];
10 double salary;
11 int floor;
12 };
13
14 template<>void Swap<job>(job &j1, job &j2);
15 void show(job &j);
16
17 int main()
18 {
19 cout.precision(2); //指定显示多少位小数(假定处于定点模式下):看前面有没有cout<<fixed,如果没有的话, 是代表2位有效数字, 不是2位小数
20 cout.setf(ios::fixed, ios::floatfield); //ios::fixed设置为定点输出格式,floatfield设置输出时按浮点格式,小数点后有6位数字
21 int i = 10, j = 20;
22 cout << "i,j= " << i << "," << j << endl;
23 Swap(i, j);
24 cout << "Now i,j= " << i << "," << j << endl;
25
26 job sue = { "S Y", 73000.60, 7 };
27 job sid = { "S T", 78060.72, 9 };
28 cout << "Before job swapping: " << endl;
29 show(sue);
30 show(sid);
31 Swap(sue, sid);
32 cout << "After job swapping: " << endl;
33 show(sue);
34 show(sid);
35 system("pause");
36 return 0;
37 }
38
39 //模板:交换a、b值
40 template <typename T>
41 void Swap(T &a, T &b) {
42 T temp = a;
43 a = b;
44 b = temp;
45 }
46
47 //显示具体化:交换结构体内部成员值
48 template<>void Swap<job>(job &j1, job &j2)
49 {
50 double t1;
51 int t2;
52 t1 = j1.salary;
53 j1.salary = j2.salary;
54 j2.salary = t1;
55
56 t2 = j1.floor;
57 j1.floor = j2.floor;
58 j2.floor = t2;
59 }
60
61 void show(job &j) //引用
62 {
63 cout << j.name << ":$" << j.salary << " on floor " << j.floor << endl;
64 }

8.5.4 实例化和具体化
隐式实例化
显示实例化
显示具体化
8.5.5 编译器选择使用哪个函数版本
重载解析:对于函数重载、函数模板和函数模板重载,C++需要一个定义良好的策略,来决定为函数调用使用哪一个函数定义,尤其是有多个参数时。
1 #include <iostream>
2 using namespace std;
3
4 template<typename T> //template A
5 void showarray(T arr[], int n);
6
7 template<typename T> //template B
8 void showarray(T *arr[], int n);
9
10 struct debts
11 {
12 char name[50];
13 double amount;
14 };
15
16 int main()
17 {
18 int things[6] = { 13,31,103,301,310,130 };
19 struct debts mr_E[3]=
20 {
21 {"I W",2400.0},
22 {"U F",1300.0},
23 {"I S",1800.0}
24 };
25 double *pd[3]; //指针数组
26 for (int i = 0; i < 3; i++)
27 {
28 pd[i] = &mr_E[i].amount;
29 }
30 cout << "Listen:" << endl;
31 showarray(things, 6);
32 cout << "Listen debts:" << endl;
33 showarray(pd, 3);
34
35 system("pause");
36 return 0;
37 }
38
39 template<typename T> //template A
40 void showarray(T arr[], int n)
41 {
42 cout << "template A" << endl;
43 for (int i = 0; i < n; i++)
44 cout << arr[i] << ' ';
45 cout << endl;
46 }
47
48 template<typename T> //template B
49 void showarray(T *arr[], int n)
50 {
51 cout << "template B" << endl;
52 for (int i = 0; i < n; i++)
53 cout << *arr[i] << ' ';
54 cout << endl;
55 }

创建自定义选择
1 #include <iostream>
2 using namespace std;
3
4 //返回小值
5 template<class T>
6 T lesser(T a, T b)
7 {
8 cout << "B";
9 return a < b ? a : b;
10 }
11
12 //先取绝对值,再返回绝对值中的小值
13 int lesser(int a, int b)
14 {
15 cout << "A";
16 a = a < 0 ? -a : a;
17 b = b < 0 ? -b : b;
18 return a < b ? a : b;
19 }
20
21 int main()
22 {
23 int m = 20;
24 int n = -30;
25 double x = 15.5;
26 double y = 25.9;
27 cout << lesser(m, n) << endl; //调用函数
28 cout << lesser(x, y) << endl; //调用模板函数
29 cout << lesser<>(m, n) << endl; //<>表示调用模板函数
30 cout << lesser<int>(x, y) << endl; //<int>表示显示实例化,强转x,y的值
31
32 system("pause");
33 return 0;
34 }


浙公网安备 33010602011771号