程序设计实习 新标准C++程序设计
第一章 从C到C++ :
引用和函数参数的传递 :
C++定义“引用”的方式如下 :
类型名 & 引用名 = 同类型的某变量名;例如 :
1
2
3int n;
int & r = n;
//r是一个引用,类型是int &注意事项 :
定义引用要初始化
只能引用变量常引用 :
前面加const,不能通过常引用修改引用的内容1
2
3
4
5int n;
const int & r = n;
//r是一个常引用,类型是const int &
r = 200;
//编译出错,不能通过常引用去修改其引用的内容参数传值和参数传引用 :
C++中函数参数传递有两种方式:传值和传引用
传值:形参表不是引用,改变形参不改变实参
传引用:形参表是引用,改变形参,实参也改变参数传值例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using namespace std;
void Swap(int a,int b)
{
int temp;
temp = a;
a = b;
b = temp;
cout << "In Swap:a = " << a << ", b = " << b << endl;
}
int main()
{
int a = 4;
int b = 5;
Swap(a,b);
cout << "After swaping:a = " << a << ", b = " << b;
return 0;
}
/*输出结果是:
In Swap:a = 5, b = 4
After swaping:a = 4, b = 5*/
Swap内部形参a,b确实发生了互换,但是在main函数中a,b还是维持原来的值。
- 参数传引用例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using namespace std;
void Swap(int &a,int &b)
{
int temp;
temp = a;
a = b;
b = temp;
}
int main()
{
int n1 = 100;
int n2 = 50;
Swap(a,b);
cout << n1 << " " << n2 << endl;
return 0;
}
/*输出结果是:
50 100
*/
传引用形参是对应实参的引用,形参和实参是同一回事,形参的改变会改变实参。
指针和动态内存分配 :
通过new运算符实现动态内存分配,用法如下:
- p = new t;
t是任意类型名,p是类型为T*的指针 - p = new t[N];
t是任意类型名,p是类型为T*的指针,N代表元素个数
- p = new t;
例如:
1
2
3
4
5
6
7
8
91.
int* p;
p = new int;
*p = 5;
2.
int *pn;
int i = 5;
pn = new int [i * 20];用delete释放动态分配的内存空间,用法如下:
- delete 指针;
- delete [] 指针;(如果new的是一个数组)
例如:
1
2
3
4
5
6
7
8
91.
int* p = new int;
*p = 5;
delete p;
2.
int* p = new int[20];
p[0] = 1;
delete [] p;
第二章 类和对象初步 :
类的定义和使用 :
类的定义方法如下:
class 类名
{
访问范围说明符:
成员变量1
成员变量2
…
成员函数声明1
成员函数声明2
…
};注意事项:
- 类的定义要以“;”结束
- 访问范围说明符一共有三种:public,private,protected
- 一个类的成员函数之间可以互相调用。类的成员函数可以重载,也可以设定参数的默认值。
成员函数的实现可以位于类的定义以外,格式如下:
返回值类型 类名::函数名()
{
语句组
}类名 对象名;
一个对象占用的内存空间大小等于其成员变量所占用的内存空间大小之和
访问对象的成员 :
- 对象名.成员名
1
2
3C r1,r2;
r1.w = 5;
r2.init(5,4); - 指针 -> 成员名
1
2
3
4
5C r1,r2;
C* p1 = &r1;
C* p2 = &r2;
p1 -> w = 5;
p2 -> init(5,4) - 引用名.成员名
1
2
3
4
5C r2;
C & rr = r2;
rr.2 = 5;
rr.init(5,4);
//rr的值改变,r2的值也改变
- 对象之间可以用“=”相互赋值,但是不能用==,!=,<,>,<=,>=,进行运算
类成员可访问范围 :
- private:用来指定私有成员。一个类的私有成员,不论是成员变量还是成员函数,都只能在该类的成员函数内部才能被访问。
- public:用来指定公有成员。一个类的公有成员在任何地方都可以被访问。
- protected:用来指定保护成员。解释详见“继承张章节。
- 如果成员前面没有访问范围说明符,则对class来说,该成员默认的被认为是私有成员;对struct来说,该成员默认的被认为是公有成员。
第二章作业:
A:编程填空:学生信息处理程序 :
描述
实现一个学生信息处理程序,计算一个学生的四年平均成绩。
要求实现一个代表学生的类,并且类中所有成员变量都是【私有的】。
补充下列程序中的 Student 类以实现上述功能。
#include
#include
#include
#include
#include
#include
using namespace std;
class Student {
// 在此处补充你的代码
};
int main() {
Student student; // 定义类的对象
student.input(); // 输入数据
student.calculate(); // 计算平均成绩
student.output(); // 输出数据
}
输入
输入数据为一行,包括:
姓名,年龄,学号,第一学年平均成绩,第二学年平均成绩,第三学年平均成绩,第四学年平均成绩。
其中姓名为由字母和空格组成的字符串(输入保证姓名不超过20个字符,并且空格不会出现在字符串两端),年龄、学号和学年平均成绩均为非负整数。信息之间用逗号隔开。
输出
输出一行数据,包括:
姓名,年龄,学号,四年平均成绩。
信息之间用逗号隔开。
样例输入
Tom Hanks,18,7817,80,80,90,70
样例输出
Tom Hanks,18,7817,80
提示
必须用类实现,其中所有成员变量都是私有的。
输出结果中,四年平均成绩不一定为整数。
1 |
|
B:Complex类的成员函数 :
描述
下面程序的输出是:
3+4i
5+6i
请补足Complex类的成员函数。不能加成员变量。
#include
#include
#include
using namespace std;
class Complex {
private:
double r,i;
public:
void Print() {
cout << r << “+” << i << “i” << endl;
}
// 在此处补充你的代码
};
int main() {
Complex a;
a = “3+4i”; a.Print();
a = “5+6i”; a.Print();
return 0;
}
输入
无
输出
3+4i
5+6i
样例输入
None
样例输出
3+4i
5+6i
1 |
|
第三章 类和对象进阶 :
构造函数的概念和作用 :
“构造函数”用于对对象进行自动初始化,是一类特殊的成员函数,其名字和类的名字一样,不写返回值
一个类可以有多个构造函数。如果类中没有单独写构造函数,则会自动生成一个没有参数的构造函数,成为“默认构造函数”
对象在生成时,会调用某个构造函数进行初始化,对象一旦生成,就再也不会在其上执行构造函数,即只有在对象生成时才会被调用。任何对象生成时,都一定会调用构造函数进行初始化。
构造函数可以重载,即可以写多个构造函数,他们的参数表不同,一般声明为public
例如:
1 | class Complex |
- 注意事项:
构造函数在数组中使用的时候,使用构造函数初始化的次数取决于数组中的元素个数。例如Csample array1[2],则要调用两次构造函数
复制构造函数 :
复制构造函数也成拷贝构造函数,只有一个参数,参数的类型是“类的引用”
默认构造函数(即无参构造函数)不一定存在,但是复制构造函数总是会存在
例如:
1 |
|
1 |
|
- 复制构造函数被调用的三种情况:
用一个对象去初始化同类的另一个对象的时候,会引发复制构造函数被调用
例如:1
2Complex c2(c1);
Complex c2 = c1;赋值不引发复制构造函数,例如:c2 = c1;
如果函数F的参数是类A的对象,那么当F被调用时,类A的复制构造函数将会被调用。作为形参的对象,是用复制构造函数进行初始化的,而且调用复制构造函数时的参数,就是调用函数时所给的实参。
例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using namespace std;
class A
{
public:
A(){};
A(A & a)
{
cout << "Copy Constructor called" << endl;
}
};
void Func(A a)
{
}
int main()
{
A a;
Func(a);
return 0;
}
/*
程序的实处结果为:
Copy Constructor called
*/如果函数的返回值是类A的对象,则函数返回时,类A的复制构造函数会被调用。作为函数返回值的对象是用复制构造函数初始化的,而调用复制构造函数时的实参就是return语句所返回的对象
例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using namespace std;
class A
{
public:
int v;
A(int n){ v = n; };
A(const A & a)
{
v = a.v;
cout << "Copy constructor called" << endl;
}
};
A Func()
{
A a(4);
return a;
}
int main()
{
cout << Func().v << endl;
return 0;
}
/*
程序输出的结果是:
Copy constructor called
4
(return a传回的时候调用复制构造函数)
*/
类型转换构造函数 :
类型转换构造函数能起到类型自动转换的作用
可以用explicit声明,则默认的转换失效
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using namespace std;
class A
{
public:
double real,imag;
A(int i)
{
cout << "IntConstructor called" <<endl;
real = i;imag = 0;
}
A(double r,double i)
{
real = r;imag = i;
}
};
int main()
{
A c1(7,8);
A c2 = 12;
c1 = 9;
cout << c1.real << "," << c1.imag << endl;
return 0;
}
/*
程序的输出结果是:
IntConstructor called
IntConstructor called
9,0
Complex(int)这个构造函数就是类型转换构造函数。可以看出,该构造函数一共被调用了两次。第一次来自于对c2的初始化,第二次来自于第20行的赋值语句。这条赋值语句的等能够接受一个整型参数。因此,编译器在处理这条赋值语句时,会在等号右边自动生成一个号两边的类型是不匹配的,之所以不会报错,就是因为Complex(int)这个类型转换构造函数能够接受一个整形参数。因此,编译器在处理这条赋值语句时,会在等号右边自动生成一个临时的Complex对象,该临时对象以9为实参,用Complex(int)构造函数初始化,然后再将☆这个临时对象的值赋给cl,也可以说是9被自动转换成一个 Complex对象然后再赋值给cl。要注意,第19行是初始化语句而不是赋值语句,编译器经优化后,往往不会将12转换成一个临时对象,而是直接以12作为参数调用Complex(int)构造函数来初始化c2。
*/
析构函数 :
析构函数是成员函数的一种,它的名字与类名相同,但在前面要加~,没有参数和返回值
如果定义类时没写析构函数,则编译器生成默认析构函数
析构函数在对象消亡时自动被调用。只要在析构函数中使用delete语句,则用new运算符分配的空间在对象消亡时被释放,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class String
{
private:
char* p;
public:
String(int n);
~String();
};
String :: ~String()
{
delete [] p;
}
String :: String(int n)
{
p = new char[n];
}
静态成员变量和静态成员函数 :
类的静态成员有两种:静态成员变量和静态成员函数。静态成员就是在定义时前面加了static关键字的成员变量/函数。例如:
1
2
3
4
5
6
7
8
9class A
{
private:
int w,h;
static int nTotalArea; //静态成员变量
static int nTotalNumber;
public:
static void PrintTotal(); //静态成员函数
};普通成员变量每个对象有各自的一份,而静态成员变量只有一份,被所有同类对象共享
静态成员无需通过对象访问,不与对象绑定而与类绑定。访问静态成员时,通过“类名 :: 成员名”访问
静态成员变量本质上是全局变量。一个类就算没有对象,其静态成员变量也存在。其目的是将某些类紧密相关的全局变量和全局函数封装形成一个整体
静态成员变量必须在类定义外面专门声明,声明时变量名前面加“类名 ::”,声明的同时可以初始化
注意事项:
- 静态成员变量必须声明或者初始化
- 静态成员变量不能和this指针混用
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using namespace std;
class A
{
private:
int w,h;
static int totalArea;
static int totalNumber;
public:
A(int w_,int h_);
~A();
static void PrintTotal();
};
A :: A(int w_,int h_)
{
w = w_;h = h_;
totalNumber ++;
totalArea += w * h;
}
A :: ~A()
{
totalNumber --;
totalArea -= w * h;
}
void A :: PrintTotal()
{
cout << totalNumber << totalArea << endl;
}
int A :: totalNumber = 0;
int A :: totalArea = 0;
//必须在定义类的文件中对静态成员变量进行一次声明或初始化,否则编译不通过
int main()
{
A r1(3,3),r2(2,2);
A :: PrintTotal();
r1.PrintTotal();
return 0;
}
/*
程序输出的结果是:
2,13
2,13
,*/静态成员函数不具体作用与某个对象,所以静态成员函数内部不能访问飞静态成员变量,也不能调用非静态成员函数
常量对象和敞亮成员函数 :
在定义该对象时可以在前面加const关键字,称为常量对象。不能通过常量对象调用普通成员函数(可能修改对象的值),但是可以通过常量对象调用常量成员函数。例如:
1
2
3
4
5
6
7class A
{
public:
void SetValue(){}
};
const A obj;
//obj.SetValue()是错误的常量成员内部也不能调用同类的其他非常量成员函数(静态成员函数除外)
成员对象和封闭类 :
- 一个类的成员变量是另一个类的对象,就称之为“成员对象”,包含成员对象的类成为“封闭类”
封闭类构造函数的初始化列表 :
在构造函数中添加初始化列表的写法如下:
类名 :: 构造函数名(参数表) : 成员变量1(参数表),成员变量2(参数表),…
{
…
}封闭类对象生成时,先执行所有成员对象的构造函数,然后才执行封闭类自己的构造函数。当封闭类对象消亡时,限制性封闭类的析构函数,然后再执行成员对象的析构函数。
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13class car
{
private:
int price;
Ctyre tyre;
Cengine engine;
public:
car(int p,int tr,int tw);
};
car :: car(int p,int tr,int tw) : price(p),tyre(tr,tw)
{
}
封闭类的构造函数 :
- 封闭类的对象,如果使用默认复制构造函数初始化的,那么它包含的成员对象也会用复制构造函数初始化美丽如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using namespace std;
class A
{
public:
A(){cout << "default" << endl;}
A(A & a) {cout << "copy" << endl;}
};
class B
{
A a;
};
int main()
{
B b1,b2(b1);
return 0;
}
/*
程序输出的结果是:
default
copy
说明b2.a是用A的复制构造函数相互实话的,而且调用复制构造函数时的实参是b1.a*/
const成员和引用成员 :
- 类还可以有常量型成员变量和引用型成员变量,这两种类型的成员变量必须在构造函数的初始化列表中进行初始化,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using namespace std;
int f;
class CDemo
{
private:
const int num; //常量型成员变量
int & ref; //引用型成员变量
int value;
public:
CDemo(int n) : num(n),ref(f),value(4)
{
}
};
int main()
{
cout << sizeof(CDemo) << endl;
return 0;
}
/*
程序输出的结果是:
12
*/
友元 :
友元函数 :
在友元函数内部可以访问该类对象的私有成员
将全局函数声明为友元的写法如下:
friend 返回值类型 函数名(参数表);将其他类的成员函数声明为友元的写法如下:
friend 返回值类型 其他类的类名 :: 成员函数名(参数表);
友元类 :
一个类A可以将另一个类B声明为自己的友元,类B的所有所有成员函数都可以访问A的私有成员。写法如下:
friend class 类名;例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Car
{
private:
int price;
friend class Driver;
};
class Driver
{
public:
Car myCar;
void ModifyCar()
{
myCar.price += 1000; //因为Driver是Car的友元类,所以可以访问其私有成员
}
};
this指针 :
this指针的作用 :
this指针指向成员函数作用的对象,通过this指针指向对象所在的地址
因为this指针的存在,非静态成员函数实际上的形参个数要比写出来的参数多一个
静态成员函数并不作用于某个对象,所以在其内部不能用this指针
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using namespace std;
class A
{
public:
double real,imag;
A(double r,double i) : real(r),imag(i) {}
A AddOne()
{
this -> real ++;
return *this;
}
};
int main()
{
A c1(1,1),c2(0,0);
c2 = c1.AddOne();
cout << c2.real << "," << c2.imag << endl; //输出2,1
return 0;
}
/*
this指针的类型是A*。因为this指针就指向函数所作用的对象,所以this -> real和real是完全等价的。*this代表函数所作用的对象,进入AddOne函数后,*this实际上就是c1.因此c2的值会变得和c1相同
*/
第三章作业 :
A:Apple :
描述
程序填空,使其输出4 5 1
#include
using namespace std;
class Apple {
// 在此处补充你的代码
static void PrintTotal() {
cout << nTotalNumber << endl;
}
};
int Apple::nTotalNumber = 0;
Apple Fun(const Apple & a) {
a.PrintTotal();
return a;
}
int main()
{
Apple * p = new Apple[4];
Fun(p[2]);
Apple p1,p2;
Apple::PrintTotal ();
delete [] p;
p1.PrintTotal ();
return 0;
}
输入
无
输出
4
5
1
样例输入
None
样例输出
4
5
1
1 |
|
B:返回什么才好呢 :
描述
程序填空,使其按要求输出
#include
using namespace std;
class A {
public:
int val;
A(int
// 在此处补充你的代码
};
int main()
{
int m,n;
A a;
cout << a.val << endl;
while(cin >> m >> n) {
a.GetObj() = m;
cout << a.val << endl;
a.GetObj() = A(n);
cout << a.val<< endl;
}
return 0;
}
输入
多组数据,每组一行,是整数 m 和 n
输出
先输出一行:
123
然后,对每组数据,输出两行,第一行是m,第二行是n
样例输入
2 3
4 5
样例输出
123
2
3
4
5
1 |
|
C:Big & Base 封闭类问题 :
描述
程序填空,输出指定结果
#include
#include
using namespace std;
class Base {
public:
int k;
Base(int n):k(n) { }
};
class Big
{
public:
int v;
Base b;
// 在此处补充你的代码
};
int main()
{
int n;
while(cin >>n) {
Big a1(n);
Big a2 = a1;
cout << a1.v << “,” << a1.b.k << endl;
cout << a2.v << “,” << a2.b.k << endl;
}
}
输入
多组数据,每组一行,是一个整数
输出
对每组数据,输出两行,每行把输入的整数打印两遍
样例输入
3
4
样例输出
3,3
3,3
4,4
4,4
1 |
|
D:奇怪的类复制 :
描述
程序填空,使其输出9 22 5
#include
using namespace std;
class Sample {
public:
int v;
// 在此处补充你的代码
};
void PrintAndDouble(Sample o)
{
cout << o.v;
cout << endl;
}
int main()
{
Sample a(5);
Sample b = a;
PrintAndDouble(b);
Sample c = 20;
PrintAndDouble(c);
Sample d;
d = a;
cout << d.v;
return 0;
}
输入
无
输出
9
22
5
样例输入
None
样例输出
9
22
5
1 |
|
第四章 运算符重载 :
运算符重载的概念和原理 :
定义:运算符重载就是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时产生不同的行为
运算符重载的实质是编写以运算符作为名称的函数,格式如下:
返回值类型 operator 运算符(参数表)
{
…
}注意事项:
- 同一个运算符可以多次被重载
- 一般来说,倾向于将运算符重载为成员函数
- 返回值类型一般为本类
运算符重载为全局函数时,参数的数目等于运算符的数目(即操作数的个数);运算符为成员函数时,参数的个数等于运算符的个数-1
重载运算符“=” :
“=”只能重载为成员函数
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
using namespace std;
class String
{
private:
char* str;
public:
String() : str(NULL) {}
const char* c_str() const
{
return str;
}
String & operator=(const char* s);
~String()
};
String :: String operator=(const char* s)
{
if(str)
{
delete [] str;
}
if(s)
{
str = new char[strlen(s) + 1];
strcpy(str,s);
}
else
{
str = NULL;
}
return *this;
}
String :: ~String()
{
if(str)
{
delete [] str;
}
};
int main()
{
String s;
s = "Good Luck,"; //等价于s.operator=("Good Luck,");
cout << s.c_str() << endl;
/*
String s2 = "Hello!"这条语句会出错,因为这是一条初始化语句,要用到构造函数,而不是重载运算符“=”
*/
return 0;
}
深拷贝与浅拷贝 :
浅拷贝:拷贝后指针指向同一地方
深拷贝:内容复制而指针指向不同地方
浅拷贝容易导致错误,一般采用深拷贝。深拷贝的原理为new一个新的空间完成复制。例如(重点需记忆):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21&String operator=(const String& s)
{
if(str == s.str)
{
return *this;
}
if(str)
{
delete [] str;
}
if(s.str)
{
str = new char[strlen(s.str + 1)];
strcpy(str,s.str);
}
else
{
str = NULL;
}
return *this;
}
运算符重载为友元函数 :
- 适用于需要访问私有成员且为全局函数(参数个数等于运算符个数)。具体写法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class A
{
double real,imag;
public:
A(double r,double i) : real(r),imag(i) {};
A operator+(double r);
friend A operator+(double r,const A & c);
};
A A :: operator+(double r) //能解释c+5
{
return A(real + r,imag);
}
A operator+(double r,const A & c)
{
return A(c.real + r,c.imag); //能解释5+c
}
重载插入流运算符和流提取运算符 :
有的时候需要重载“<<”和“>>”,返回值和第一个参数类型为ostream&和istream&,格式如下:
ostream & ostream :: operator<<(…)
{
//一些代码
return *this;
}重点记忆:
1
2
3
4
5
6
7
8
9
10
11friend ostream & operator<<(ostream & os,const A & c)
{
os << ...;
return *this;
}
friend istream & operator<<(istream & is,A & c) //不能有const
{
is << ...;
return *this;
}
重载类型强制转换运算符 :
- 例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using namespace std;
class A
{
double real,imag;
public:
A(double r = 0,double i = 0) : real (r),imag(i) {}
operator double() //重载强制类型转换运算符double
{
return real;
}
};
int main()
{
A(1.2,2.3);
cout << (double)c << endl; //输出1.2
double n = 2 + c; //等价于double n = 2 + c.operator double()
cout << n; //输出3.2
return 0;
}
重载自增自减运算符 :
- “++”、“–”都可以被重载,但是有前置、后置之分。前置时,调用参数个数正常的重载函数;处理后置表达式时,允许写一个增加了无用int类型,调用多一个参数的重载函数。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
using namespace std;
class A
{
private:
int n;
public:
A(int i = 0) : n(i) {}
A & operator++(); //用于前置格式
A & operator++(int); //用于后置格式
operator int()
{
return n;
}
friend A & operator--(A &); //用于前置格式
friend A & operator--(A &,int);//用于后置格式
};
A & A :: operator++() //前置++
{
n++;
return *this;
}
A & A :: operator++(int) //后置++
{
A temp(*this);
n++;
return temp;
}
A & operator--(A & d) //前置--
{
d.n--;
return d;
}
A & operator--(A & d,int) //后置--
{
A temp(d)
d.n--;
return temp;
}
第四章作业 :
A:MyString :
描述
补足MyString类,使程序输出指定结果
#include
#include
#include
using namespace std;
class MyString {
char * p;
public:
MyString(const char * s) {
if( s) {
p = new char[strlen(s) + 1];
strcpy(p,s);
}
else
p = NULL;
}
~MyString() { if(p) delete [] p; }
// 在此处补充你的代码
};
int main()
{
char w1[200],w2[100];
while( cin >> w1 >> w2) {
MyString s1(w1),s2 = s1;
MyString s3(NULL);
s3.Copy(w1);
cout << s1 << “,” << s2 << “,” << s3 << endl;
s2 = w2;
s3 = s2;
s1 = s3;
cout << s1 << "," << s2 << "," << s3 << endl;
}
}
输入
多组数据,每组一行,是两个不带空格的字符串
输出
对每组数据,先输出一行,打印输入中的第一个字符串三次
然后再输出一行,打印输入中的第二个字符串三次
样例输入
abc def
123 456
样例输出
abc,abc,abc
def,def,def
123,123,123
456,456,456
1 |
|
B:看上去好坑的运算符重载 :
描述
程序填空
#include
using namespace std;
class MyInt
{
int nVal;
public:
MyInt( int n) { nVal = n ;}
// 在此处补充你的代码
};
int Inc(int n) {
return n + 1;
}
int main () {
int n;
while(cin >>n) {
MyInt objInt(n);
objInt-2-1-3;
cout << Inc(objInt);
cout <<”,”;
objInt-2-1;
cout << Inc(objInt) << endl;
}
return 0;
}
输入
多组数据,每组一行,整数n
输出
对每组数据,输出一行,包括两个整数, n-5和n - 8
样例输入
20
30
样例输出
15,12
25,22
1 |
|
C:惊呆!Point竟然能这样输入输出 :
描述
程序填空
#include
using namespace std;
class Point {
private:
int x;
int y;
public:
Point() { };
// 在此处补充你的代码
};
int main()
{
Point p;
while(cin >> p) {
cout << p << endl;
}
return 0;
}
输入
多组数据,每组两个整数
输出
对每组数据,输出一行,就是输入的两个整数
样例输入
2 3
4 5
样例输出
2,3
4,5
1 |
|
D:二维数组类 :
描述
写一个二维数组类 Array2,使得下面程序的输出结果是:
0,1,2,3,
4,5,6,7,
8,9,10,11,
next
0,1,2,3,
4,5,6,7,
8,9,10,11,
程序:
#include
#include
using namespace std;
class Array2 {
// 在此处补充你的代码
};
int main() {
Array2 a(3,4);
int i,j;
for( i = 0;i < 3; ++i )
for( j = 0; j < 4; j ++ )
a[i][j] = i * 4 + j;
for( i = 0;i < 3; ++i ) {
for( j = 0; j < 4; j ++ ) {
cout << a(i,j) << “,”;
}
cout << endl;
}
cout << “next” << endl;
Array2 b; b = a;
for( i = 0;i < 3; ++i ) {
for( j = 0; j < 4; j ++ ) {
cout << b[i][j] << “,”;
}
cout << endl;
}
return 0;
}
输入
无
输出
0,1,2,3,
4,5,6,7,
8,9,10,11,
next
0,1,2,3,
4,5,6,7,
8,9,10,11,
样例输入
None
样例输出
0,1,2,3,
4,5,6,7,
8,9,10,11,
next
0,1,2,3,
4,5,6,7,
8,9,10,11,
1 |
|
E:别叫,这个大整数已经很简化了! :
描述
程序填空,输出指定结果
#include
#include
#include
#include
using namespace std;
const int MAX = 110;
class CHugeInt {
// 在此处补充你的代码
};
int main()
{
char s[210];
int n;
while (cin >> s >> n) {
CHugeInt a(s);
CHugeInt b(n);
cout << a + b << endl;
cout << n + a << endl;
cout << a + n << endl;
b += n;
cout << ++ b << endl;
cout << b++ << endl;
cout << b << endl;
}
return 0;
}
输入
多组数据,每组数据是两个非负整数s和 n。s最多可能200位, n用int能表示
输出
对每组数据,输出6行,内容对应程序中6个输出语句
样例输入
99999999999999999999999999888888888888888812345678901234567789 12
6 6
样例输出
99999999999999999999999999888888888888888812345678901234567801
99999999999999999999999999888888888888888812345678901234567801
99999999999999999999999999888888888888888812345678901234567801
25
25
26
12
12
12
13
13
14
1 |
|
第五章 继承与派生 :
继承和派生的概念 :
可以把类A作为一个“基类”(也称“父类”),把类B写为基类A的一个“派生类”(也称“子类”)。这样就可以说从类A“派生”出了类“B”,,也可以说类B“继承”了类A
基类的所有成员自动成为派生类的成员。派生类可以对基类进行扩充和修改:扩充指的是在派生类中可以添加新的成员变量和成员函数,修改指的是在派生类中可以重写基类继承得到的成员
派生类的成员函数不能访问基类的私有成员
派生类写法如下:
class 派生名 : 继承方式说明符 基类名
{
…
};派生类对象中包含基类对象,而且基类对象的存储位于派生类对象新增的成员变量之前
在派生类中调用基类同名成员格式为:基类 :: 成员函数。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
using namespace std;
class student
{
private:
string name;
string id; //学号
char gender; //性别,F代表女,M代表男
int age;
public:
void PrintInfo();
void SetInfo(const string & name_,const string & id_,int age_,char gender_);
string GetName()
{
return name;
}
};
class Undergraduate : public student
{
private:
string department;
public:
void Qualified()
{
cout << "qualified for baoyan" << endl;
}
void PrintInfo()
{
student :: PrintInfo(); //调用基类的PrintInfo函数
cout << "Department:" << department << endl;
}
void SetInfo(const string & name_,const string & id_,int age_,char gender_,const string & department_)
{
student :: SetInfo(name_,id_,age_,gender_); //调用基类的SetInfo函数
department = department_;
}
};
void student :: PrintInfo()
{
cout << "Name:" << name << endl;
cout << "ID:" << id << endl;
cout << "Age:" << age << endl;
cout << "Gender:" << gender << endl;
}
void student :: SetInfo(const string & name_,const string & id_,int age_,char gender_)
{
name = name_;
id = id_;
age = age_;
gender = gender_;
}
int main()
{
...
return 0;
}如果基类 :: …报错,则需要定义为static
派生类的构造函数和析构函数 :
派生类对象在创建时,除了要调用自身的构造函数进行初始化外,还要先执行基类的构造函数初始化其包含的基类对象。
程序中任何生成派生类对象的语句都要先说明其包含的基类对象是如何初始化的。格式如下:
构造函数名(形参表) : 基类名(基类构造函数形参表)
{}
如果一个派生类对象是用默认复制构造函数初始化的,那么它内部包含的基类对象也要用复制构造函数初始化
构造函数调用顺序:自上而下执行所有基类的构造函数,再按照成员对象的定义顺序执行各个成员对象的构造函数,再执行自身的构造函数;析构函数的调用顺序:先执行自身的析构函数,然后次序相反的依次执行所有成员对象的析构函数,最后再从底向上执行各个基类的析构函数
基类与派生类指针的互相转换 :
即便基类指针指向的是一个派生类对象,也不能通过基类指针访问基类没有而派生类中有的成员
基类的指针不能赋值给派生类的指针,但是通过强制类型转换,也可以将基类指针转换成派生类指针后再赋值给派生类的指针。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using namespace std;
class CBase
{
protected:
int n;
public:
CBase(int i) : n(i) {}
void Print() {cout << "CBase n = " << n << endl;}
};
class CDrived : public CBase
{
public:
int v;
CDerived(int i) : Cbase(i),v(2*i) {}
void Func() {};
void Print()
{
cout << "CDerived:n=" << n << endl;
cout << "CDerived:v=" << v << endl;\
}
};
int main()
{
CDerived objDerived(3);
CBase objBase(5);
CBase * pBase = & objDerived; //基类指针指向派生类对象
//pBase -> Func(); //错,CBase中没有Func函数
//pBase -> v = 5; //错,CBase中没有v成员变量
pBase -> Print();
//CDerived * pDerived = & objBase; //错,不能将基类指针赋值给派生类指针
CDerived * pDerived = (CDerived*) (&objBase);
objDerived.Print();
return 0;
}
多层次派生 :
派生是可以多层次的,例如类A派生类B,类B再派生类C…称类A是类B的直接基类,类A是类C的间接基类。在定义派生类时,只写直接基类,不写间接基类
派生类的成员包括派生类自己的定义的成员、这届基类中定义的成员,以及所有间接基类的全部成员
公有派生、保护派生和私有派生 :
基类的保护成员可以再派生类的成员函数中被访问,但是不能再继承类以外的地方被访问,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29class CBase
{
private:
int nPrivate;
public:
int nPublic:
protected:
int nProtected;
};
class CDerived : public CBase
{
void AccessBase()
{
nPublic = 1; //OK
nPrivate = 1; //错,不能访问基类私有成员
nProtected = 1; //OK,访问从基类继承的protected成员
CBase f;
f.nProtected = 1 //错,f不是函数所作用的对象
}
};
int main()
{
CBase b;
CDerived d;
int n = b.nProtected; //错,不在派生类成员函数内,不能访问基类的保护成员
n = d.nPrivate; //错,不能访问d的私有成员
int m = d.nPublic; //OK
return 0;
}重点:两个表格
派生方式 | 公有成员 | 私有成员 | 保护成员 |
---|---|---|---|
public | public | 不可访问 | protected |
private | private | 不可访问 | private |
protected | protected | 不可访问 | protected |
基类成员 | 可访问 |
---|---|
private | 基类的成员函数&基类的友元函数 |
public | 基类的成员函数&基类的友元函数&派生类的成员函数&派生类的友元函数&其他函数 |
protected | 基类的成员函数&基类的友元函数&派生类的成员函数 |
第五章作业 :
A:全面的MyString :
描述
程序填空,输出指定结果
#include
#include
using namespace std;
int strlen(const char * s)
{ int i = 0;
for(; s[i]; ++i);
return i;
}
void strcpy(char * d,const char * s)
{
int i = 0;
for( i = 0; s[i]; ++i)
d[i] = s[i];
d[i] = 0;
}
int strcmp(const char * s1,const char * s2)
{
for(int i = 0; s1[i] && s2[i] ; ++i) {
if( s1[i] < s2[i] )
return -1;
else if( s1[i] > s2[i])
return 1;
}
return 0;
}
void strcat(char * d,const char * s)
{
int len = strlen(d);
strcpy(d+len,s);
}
class MyString
{
// 在此处补充你的代码
};
int CompareString( const void * e1, const void * e2)
{
MyString * s1 = (MyString * ) e1;
MyString * s2 = (MyString * ) e2;
if( * s1 < *s2 )
return -1;
else if( *s1 == *s2)
return 0;
else if( *s1 > *s2 )
return 1;
}
int main()
{
MyString s1(“abcd-“),s2,s3(“efgh-“),s4(s1);
MyString SArray[4] = {“big”,”me”,”about”,”take”};
cout << “1. “ << s1 << s2 << s3<< s4<< endl;
s4 = s3;
s3 = s1 + s3;
cout << “2. “ << s1 << endl;
cout << “3. “ << s2 << endl;
cout << “4. “ << s3 << endl;
cout << “5. “ << s4 << endl;
cout << “6. “ << s1[2] << endl;
s2 = s1;
s1 = “ijkl-“;
s1[2] = ‘A’ ;
cout << “7. “ << s2 << endl;
cout << “8. “ << s1 << endl;
s1 += “mnop”;
cout << “9. “ << s1 << endl;
s4 = “qrst-“ + s2;
cout << “10. “ << s4 << endl;
s1 = s2 + s4 + “ uvw “ + “xyz”;
cout << “11. “ << s1 << endl;
qsort(SArray,4,sizeof(MyString),CompareString);
for( int i = 0;i < 4;i ++ )
cout << SArray[i] << endl;
//s1的从下标0开始长度为4的子串
cout << s1(0,4) << endl;
//s1的从下标5开始长度为10的子串
cout << s1(5,10) << endl;
return 0;
}
输入
无
输出
- abcd-efgh-abcd-
- abcd-
- abcd-efgh-
- efgh-
- c
- abcd-
- ijAl-
- ijAl-mnop
- qrst-abcd-
- abcd-qrst-abcd- uvw xyz
about
big
me
take
abcd
qrst-abcd-
样例输入
无
样例输出 - abcd-efgh-abcd-
- abcd-
- abcd-efgh-
- efgh-
- c
- abcd-
- ijAl-
- ijAl-mnop
- qrst-abcd-
- abcd-qrst-abcd- uvw xyz
about
big
me
take
abcd
qrst-abcd-
1 |
|
B:继承自string的MyString :
描述
程序填空,输出指定结果
#include
#include
#include
#include
using namespace std;
class MyString:public string
{
// 在此处补充你的代码
};
int main()
{
MyString s1(“abcd-“),s2,s3(“efgh-“),s4(s1);
MyString SArray[4] = {“big”,”me”,”about”,”take”};
cout << “1. “ << s1 << s2 << s3<< s4<< endl;
s4 = s3;
s3 = s1 + s3;
cout << “2. “ << s1 << endl;
cout << “3. “ << s2 << endl;
cout << “4. “ << s3 << endl;
cout << “5. “ << s4 << endl;
cout << “6. “ << s1[2] << endl;
s2 = s1;
s1 = “ijkl-“;
s1[2] = ‘A’ ;
cout << “7. “ << s2 << endl;
cout << “8. “ << s1 << endl;
s1 += “mnop”;
cout << “9. “ << s1 << endl;
s4 = “qrst-“ + s2;
cout << “10. “ << s4 << endl;
s1 = s2 + s4 + “ uvw “ + “xyz”;
cout << “11. “ << s1 << endl;
sort(SArray,SArray+4);
for( int i = 0;i < 4;i ++ )
cout << SArray[i] << endl;
//s1的从下标0开始长度为4的子串
cout << s1(0,4) << endl;
//s1的从下标5开始长度为10的子串
cout << s1(5,10) << endl;
return 0;
}
输入
无
输出
- abcd-efgh-abcd-
- abcd-
- abcd-efgh-
- efgh-
- c
- abcd-
- ijAl-
- ijAl-mnop
- qrst-abcd-
- abcd-qrst-abcd- uvw xyz
about
big
me
take
abcd
qrst-abcd-
样例输入
无
样例输出 - abcd-efgh-abcd-
- abcd-
- abcd-efgh-
- efgh-
- c
- abcd-
- ijAl-
- ijAl-mnop
- qrst-abcd-
- abcd-qrst-abcd- uvw xyz
about
big
me
take
abcd
qrst-abcd-
提示
1:如果将程序中所有 “MyString” 用 “string” 替换,那么除
了最后两条红色的语句编译无法通过外,其他语句都没有问题,而且输出和前
面给的结果吻合。也就是说,MyString 类对 string 类的功能扩充只体现在最
后两条语句上面。
提示 2: string 类有一个成员函数 string substr(int start,int
length); 能够求从 start 位置开始,长度为 length 的子串
提示 3: C++中,派生类的对象可以赋值给基类对象,因为,一个派生
类对象,也可看作是一个基类对象(大学生是学生)。反过来则不行(学生未
必是大学生) 同样,调用需要基类对象作参数的函数时,以派生类对象作为实参,也是没有问题的
1 |
|
第六章 多态和虚函数 :
多态的基本概念 :
对于通过基类指针调用基类和派生类中都有的同名、同参数的虚函数的语句,如果基类指针指向的是一个基类对象,则基类的虚函数被调用;如果基类指针指向的是一个派生类对象,则派生类的虚函数被调用,这种机制被称为“多态”
虚函数是函数声明前加了virtual关键词的成员函数,virtual关键字只在类定义中的成员函数声明处使用,不能在类外部写成员函数体时使用
静态成员函数不能是虚函数
包含虚函数的类被称为“多态类”,例如
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using namespace std;
class A
{
public:
virtual void Print()
{
cout << "A::Print" << endl;
}
};
class B : public A
{
public:
virtual void Print()
{
cout << "B::Print" << endl;
}
};
class D : public A
{
public:
virtual void Print()
{
cout << "D::Print" << endl;
}
};
class E : public A
{
virtual void Print()
{
cout << "E::Print" << endl;
}
};
int main()
{
A a;B b;D d;E e;
A* pa = & a;B* pb = & b;
pa -> Print(); //多态,a.Print()被调用,输出A::Print
pa = pb; //基类指针pa指向派生类对象b
pa -> Print(); //b.Print()被调用,输出B::Print
pa = & d; //基类指针pa指向派生类对象d
pa -> Print(); //多态,d.Print()被调用,输出D::Print
pa = & e; //基类指针指向派生类对象e
pa -> Print(); //多态,e.Print()被调用
return 0;
}
/*
程序的输出结果是:
A::Print
B::Print
D::Print
E::Print
*/
通过基类引用实现多态 :
通过基类的引用调用虚函数的语句也是多态的。即通过基类的引用调用基类和派生类的同名、同参数表时,若其引用的是一个基类的对象,则被调用是基类的虚函数;若其引用的是一个派生类的对象,则被调用的是派生类的虚函数。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using namespace std;
class A
{
public:
virtual void Print()
{
cout << "A::Print" << endl;
}
};
class B : public A
{
public:
virtual void Print()
{
cout << "B::Print" << endl;
}
};
void PrintInfo(A & r)
{
r.Print(); //多态,调用哪个Print,取决于r引用了那个类型的对象
}
int main()
{
A a;
B b;
PrintInfo(a); //输出A::Print
PrintInfo(b); //输出B::Print
return 0;
}传引用体现了多态的主要作用:增强程序的可扩充性,精简代码
关于多态的注意事项
在成员函数(静态成员函数、构造函数和析构函数除外)中调用其他虚成员函数时多态的,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using namespace std;
class CBase
{
public:
void func1()
{
func2();
}
virtual void func2()
{
cout << "CBase::func2()" << endl;
}
};
class CDerived : public CBase
{
public:
virtual void func2()
{
cout << "CDerived:func2()" << endl;
}
};
int main()
{
CDerived d;
d.func1();
return 0;
}
/*
程序的输出结果是:
CDerived: func2()
*/在构造函数和析构函数中调用虚函数不是多态
只要积累中的某干函数被声明为虚函数,则派生类的同名同参的成员函数即使前面不写virtual,也自动成为虚函数
只要基类的析构函数是虚函数,那么派生类的析构函数不论是否使用virtual来声明,都自动成为虚析构函数
一般来说,一个类如果定义了虚函数,则最好也将虚构函数定义为虚函数。析构函数可以是不函数,但是构造函数和静态成员函数不能是虚函数
可以在虚函数中使用this指针
纯虚函数和抽象类
纯虚函数就是没有函数体的虚函数。包含纯虚函数的类就叫抽象类
1
2
3
4
5
6
7
8
9
10
11class A
{
private:
int a;
public:
virtual void Print() = 0;
void fun1()
{
cout << "fun1";
}
};抽象类不能生成独立的对象,例如:A a;A * p = new A;A a[2];等语句都会编译出错
抽象类的对象不存在,但是被包含在派生类对象中的抽象类的对象是可以存在的
类函数的初始化常用:T a = T();
第六章作业 :
A:看上去像多态 :
描述
程序填空产生指定输出
#include
using namespace std;
class B {
private:
int nBVal;
public:
void Print()
{ cout << “nBVal=”<< nBVal << endl; }
void Fun()
{cout << “B::Fun” << endl; }
B ( int n ) { nBVal = n;}
};
// 在此处补充你的代码
int main() {
B * pb; D * pd;
D d(4); d.Fun();
pb = new B(2); pd = new D(8);
pb -> Fun(); pd->Fun();
pb->Print (); pd->Print ();
pb = & d; pb->Fun();
pb->Print();
return 0;
}
输入
无
输出
D::Fun
B::Fun
D::Fun
nBVal=2
nBVal=24
nDVal=8
B::Fun
nBVal=12
样例输入
无
样例输出
D::Fun
B::Fun
D::Fun
nBVal=2
nBVal=24
nDVal=8
B::Fun
nBVal=12
1 |
|
B:Fun和Do :
描述
程序填空输出指定结果
#include
using namespace std;
class A {
private:
int nVal;
public:
void Fun()
{ cout << “A::Fun” << endl; };
void Do()
{ cout << “A::Do” << endl; }
};
class B:public A {
public:
virtual void Do()
{ cout << “B::Do” << endl;}
};
class C:public B {
public:
void Do( )
{ cout <<”C::Do”<<endl; }
void Fun()
{ cout << “C::Fun” << endl; }
};
void Call(
// 在此处补充你的代码
) {
p.Fun(); p.Do();
}
int main() {
C c;
Call( c);
return 0;
}
输入
无
输出
A::Fun
C::Do
样例输入
None
样例输出
A::Fun
C::Do
1 |
|
C:这是什么鬼delete :
描述
程序填空输出指定结果
#include
using namespace std;
class A
{
public:
A() { }
// 在此处补充你的代码
};
class B:public A {
public:
~B() { cout << “destructor B” << endl; }
};
int main()
{
A * pa;
pa = new B;
delete pa;
return 0;
}
输入
无
输出
destructor B
destructor A
样例输入
无
样例输出
destructor B
destructor A
1 |
|
D:怎么又是Fun和Do :
描述
程序填空输出指定结果
#include
using namespace std;
class A {
private:
int nVal;
public:
void Fun()
{ cout << “A::Fun” << endl; };
virtual void Do()
{ cout << “A::Do” << endl; }
};
class B:public A {
public:
virtual void Do()
{ cout << “B::Do” << endl;}
};
class C:public B {
public:
void Do( )
{ cout <<”C::Do”<<endl; }
void Fun()
{ cout << “C::Fun” << endl; }
};
void Call(
// 在此处补充你的代码
) {
p->Fun(); p->Do();
}
int main() {
Call( new A());
Call( new C());
return 0;
}
输入
无
输出
A::Fun
A::Do
A::Fun
C::Do
样例输入
无
样例输出
A::Fun
A::Do
A::Fun
C::Do
1 |
|
第九章 泛型程序设计与模板 :
函数模板 :
函数模板的作用:提高程序的可重用性和可扩充性
模板分为函数模板和类模板。函数模板是用来生成函数的,类模板是用来生成类的。函数模板的写法如下:
template<class 类型参数1,class 类型参数2>
返回值类型 模板名(形参表)
{
函数体
}
其中形参表可以直接使用template中的类型参数。模板名就是函数名。其中class关键字也可以用typename关键字替代编译器由模板生成函数时,会用具体的类型名对模板中所有的类型参数进行替换,其他部分则原封不动保留。编译器在编译到调用函数模板的语句时,会根据实参的类型判断该如何替换模板中的类型参数。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using namespace std;
template <class T>
void Swap(T & x,T & y)
{
T temp = x;
x = y;
y = temp;
}
int main()
{
int n = 1,m = 2;
Swap(n,m); //编译器自动生成void Swap(int &,int &)函数
double f = 1.2,g = 2.3;
Swap(f,g); //编译器自动生成void Swap(double &,double &)函数
return 0;
}编译器由模板自动生成函数的过程叫模板的实例化,由模板实例化而得到的函数成为模板函数
模板调用语句可以指明要把参数类型实例化为哪种类型,可以用:
模板名<实际参数类型1,实际参数类型2…>。例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using namespace std;
template<class T>
T Inc(int n)
{
return 1 + n;
}
int main()
{
cout << Inc<double>(4) / 2;
return 0;
}
/*
Inc<dounle>(4)指明了实例化的函数原型为:double Inc(double)
*/在函数模板中,类型参数不但可以用来定义参数的类型,还可以用于定于局部变量和函数模板的返回值
函数模板可以重载,只要他们的形参表不同即可。函数调用语句匹配函数模板遵循以下先后顺序:
- 先找参数完全匹配的普通函数(非由模板实例化得到的函数)
- 再找参数完全匹配的模板函数
- 再找实参经过自动类型转换后能够匹配的普通函数
- 若以上都找不到,则报错
例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using namespace std;
template <class T>
T Max(T a,T b)
{
cout << "Template Max 1" << endl;
return 0;
}
template<class T,class T2>
T Max(T a,T2 b)
{
cout << "Template Max 2" <<endl;
return 0;
}
double Max(double a,double b)
{
cout << "Function Max" << endl;
return 0;
}
int main()
{
int i = 4,j = 5;
Max(1.2,3.4); //调用Max函数
Max(i,j); //调用第一个Max模板生成的函数
Max(1.2,3); //调用第二个Max模板生成的函数
return 0;
}
/*
程序的输出结果是:
Function Max
Template Max 1
Template Max 2
*/
类模板 :
类模板写法如下:
template<类型参数表>
class 类模板名
{
成员函数和成员变量
};类型参数表的写法如下:
class 类型参数1,class 类型参数2,…类模板中成员函数放到类模板定义外面写法如下:
template<类型参数表>
返回值类型 类模板名<类型参数名列表> :: 成员函数名(参数表)
{
…
}编译器由类模板生成类的过程叫类模板的实例化。由类模板实例化得到的类叫模板类。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using namespace std;
template <class T1,class T2>
class Pair
{
public:
T1 key;
T2 value;
Pair(T1 k,T2 v) : key(k),value(v) {}
bool operator < (const Pair<T1,T2> & p) const;
};
template<class T1,class T2>
bool Pair(T1,T2) :: operator < (const Pair<T1,T2> & p) const
//Pair 的成员函数operator <
{
return key < p.key;
}
int main()
{
Pair<string,int>student("Tom",19); //实例化一个类Pair<string,int>
cout << student.key << " " << student.value;
return 0;
}类模板中的成员函数还可以是一个函数模板。成员函数模板只有在被调用时才会被实例化。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using namespace std;
template <class T>
class A
{
public:
template <class T2>
void Func(T2 t)
{
cout << t;
}
};
int main()
{
A<int>a;
a.Func('k'); //成员函数模板Func被实例化
a.Func("Hello");
return 0;
}
类模板中的非类型参数 :
- 类模板的“类型参数表”中可以出现非类型参数,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14template <class T,int size>
class CArray
{
T array[size];
public:
void Print()
{
for(int i = 0;i < size;i++)
{
cout << array[i] << endl;
}
}
};
//注意:CArray<int,40>和CArray<int,50>完全是两个类,这两个类的对象之间不能赋值
类模板与继承 :
- 类模板和类模板之间、类模板和类之间可以互相继承
类模板从类模板派生 :
1 | template <class T1,class T2> |
类模板从模板类派生 :
1 | template<class T1,class T2> |
类模板从普通类派生 :
1 | class A |
普通类从模板类派生 :
1 | template <class T> |
类模板中的静态成员 :
- 类模板中可以定义静态成员,从该类模板实例化得到的所有类都包含同样的静态成员。但是不同实例化相当于不同类,不会共享静态成员,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
using namespace std;
template <class T>
class A
{
private:
static int count;
public:
A()
{
count ++;
}
~A()
{
count --;
}
static void PrintCount()
{
cout << count << endl;
}
};
template<>int A<int> :: count = 0;
template<>int A<double> :: count = 0;
int main()
{
A<int> ia;
A<double> da;
ia.PrintCount();
da.PrintCount();
return 0;
}
/*
程序输出的结果是:
1
1
A<int>的对象ia和A<double>的对象da不会共享一份count
*/
第九章作业 :
A:简单的SumArray :
描述
填写模板 PrintArray,使得程序输出结果是: TomJackMaryJohn 10 不得编写SumArray函数
#include
#include
using namespace std;
template
T SumArray(
// 在此处补充你的代码
}
int main() {
string array[4] = { “Tom”,”Jack”,”Mary”,”John”};
cout << SumArray(array,array+4) << endl;
int a[4] = { 1, 2, 3, 4}; //提示:1+2+3+4 = 10
cout << SumArray(a,a+4) << endl;
return 0;
}
输入
无
输出
TomJackMaryJohn
10
1 |
|
B:简单的foreach :
描述
编写MyForeach模板,使程序按要求输出 不得编写 MyForeach函数
#include
#include
using namespace std;
// 在此处补充你的代码
void Print(string s)
{
cout << s;
}
void Inc(int & n)
{
++ n;
}
string array[100];
int a[100];
int main() {
int m,n;
while(cin >> m >> n) {
for(int i = 0;i < m; ++i)
cin >> array[i];
for(int j = 0; j < n; ++j)
cin >> a[j];
MyForeach(array,array+m,Print);
cout << endl;
MyForeach(a,a+n,Inc);
for(int i = 0;i < n; ++i)
cout << a[i] << “,”;
cout << endl;
}
return 0;
}
输入
多组数据
每组数据第一行是两个整数 m 和 n ,都不超过 50
第二行是m个不带空格的字符串
第三行是 n个整数
输出
对每组数据
第一行输出所有输入字符串连在一起的结果
第二行输出输入中的每个整数加1的结果
样例输入
3 4
Tom Mike Jack
1 2 3 4
1 2
Peking
100 200
样例输出
TomMikeJack
2,3,4,5,
Peking
101,201,
1 |
|
C:简单的Filter :
描述
编写Filter模板,使得程序产生指定输出 不得编写 Filter函数
#include
#include
using namespace std;
// 在此处补充你的代码
bool LargerThan2(int n)
{
return n > 2;
}
bool LongerThan3(string s)
{
return s.length() > 3;
}
string as1[5] = {“Tom”,”Mike”,”Jack”,”Ted”,”Lucy”};
string as2[5];
int a1[5] = { 1,2,3,4,5};
int a2[5];
int main() {
string * p = Filter(as1,as1+5,as2,LongerThan3);
for(int i = 0;i < p - as2; ++i)
cout << as2[i];
cout << endl;
int * p2 = Filter(a1,a1+5,a2,LargerThan2);
for(int i = 0;i < p2-a2; ++i)
cout << a2[i] << “,”;
return 0;
}
输入
无
输出
MikeJackLucy
3,4,5,
样例输入
无
样例输出
MikeJackLucy
3,4,5,
1 |
|
E:山寨版istream_iterator :
描述
模仿C++标准模板库istream_iterator用法,实现CMyistream_iterator使得程序按要求输出
#include
#include
using namespace std;
template
class CMyistream_iterator
{
// 在此处补充你的代码
};
int main()
{
int t;
cin >> t;
while( t – ) {
CMyistream_iterator
int n1,n2,n3;
n1 = * inputInt; //读入 n1
int tmp = * inputInt;
cout << tmp << endl;
inputInt ++;
n2 = * inputInt; //读入 n2
inputInt ++;
n3 = * inputInt; //读入 n3
cout << n1 << “ “ << n2<< “ “ << n3 << “ “;
CMyistream_iterator
string s1,s2;
s1 = * inputStr;
inputStr ++;
s2 = * inputStr;
cout << s1 << “ “ << s2 << endl;
}
return 0;
}
输入
第一行是整数t,表示有t组数据
每组数据一行,三个整数加两个字符串。字符串是不含空格的
输出
对每组数据,输出二行
在第一行输出第一个数
第二行原样输出输入的内容
样例输入
2
79 90 20 hello me
12 34 19 take up
样例输出
79
79 90 20 hello me
12
12 34 19 take up
提示
C++标准模板库 istream_iterator模版使用说明:
其构造函数执行过程中就会要求输入,然后每次执行++,则读取输入流中的下一个项目,执行 * 则返回上次从输入流中读取的项目。例如,下面程序运行时,就会等待用户输入数据,输入数据后程序才会结束:
#include
#include
using namespace std;
int main() {
istream_iterator inputInt(cin);
return 0;
}
下面程序运行时,如果输入 12 34 程序输出结果是: 12,12
#include
#include
using namespace std;
int main()
{
istream_iterator inputInt(cin);
cout << * inputInt << “,” << * inputInt << endl;
return 0;
}
下面程序运行时,如果输入 12 34 56程序输出结果是: 12,56
#include
#include
using namespace std;
int main()
{
istream_iterator inputInt(cin);
cout << * inputInt << “,” ;
inputInt ++;
inputInt ++;
cout << * inputInt;
return 0;
}
1 |
|
F:这个模板并不难 :
描述
程序填空,输出指定结果
#include
#include
#include
using namespace std;
template
class myclass {
// 在此处补充你的代码
~myclass( ) {
delete [] p;
}
void Show()
{
for( int i = 0;i < size;i ++ ) {
cout << p[i] << “,”;
}
cout << endl;
}
};
int a[100];
int main() {
char line[100];
while( cin >> line ) {
myclass
obj.Show();
int n;
cin >> n;
for(int i = 0;i < n; ++i)
cin >> a[i];
myclass
obj2.Show();
}
return 0;
}
输入
多组数据。每组第一行是一个不含空格的字符串
第二行是整数n
第三行是n个整数
输出
对每组数据,先依次输出输入字符串的每个字母,并且在每个字母后面加逗号
然后依次再输出输入的n个整数 ,在每个整数后面加逗号
样例输入
Tom
3
3 4 5
Jack
4
1 2 3 4
样例输出
T,o,m,
3,4,5,
J,a,c,k,
1,2,3,4,
1 |
|
G:排序,又见排序! :
描述
自己编写一个能对任何类型的数组进行排序的mysort函数模版。只能写一个mysort模板,不能写mysort函数!
#include
using namespace std;
bool Greater2(int n1,int n2)
{
return n1 > n2;
}
bool Greater1(int n1,int n2)
{
return n1 < n2;
}
bool Greater3(double d1,double d2)
{
return d1 < d2;
}
template <class T1,class T2>
void mysort(
// 在此处补充你的代码
#define NUM 5
int main()
{
int an[NUM] = { 8,123,11,10,4 };
mysort(an,an+NUM,Greater1); //从小到大排序
for( int i = 0;i < NUM; i ++ )
cout << an[i] << “,”;
mysort(an,an+NUM,Greater2); //从大到小排序
cout << endl;
for( int i = 0;i < NUM; i ++ )
cout << an[i] << “,”;
cout << endl;
double d[6] = { 1.4,1.8,3.2,1.2,3.1,2.1};
mysort(d+1,d+5,Greater3); //将数组从下标1到下标4从小到大排序
for( int i = 0;i < 6; i ++ )
cout << d[i] << “,”;
return 0;
}
输入
无
输出
4,8,10,11,123,
123,11,10,8,4,
1.4,1.2,1.8,3.1,3.2,2.1,
样例输入
无
样例输出
4,8,10,11,123,
123,11,10,8,4,
1.4,1.2,1.8,3.1,3.2,2.1,
1 |
|
第十章 标准模板库STL :
STL中的基本概念 :
容器:用于存放数据的类模板
迭代器:用于存取容器中存放的元素的工具。迭代器是一个变量,作用类似于指针。访问容器内的元素需要通过迭代器,迭代器相当于一个中介。
算法:用来操作容器中元素的函数模板。例如int array[1000]是一个容器,而int*可以作为迭代器。使用sort算法可以对这个容器进行排序:sort(array,array + 100);
容器模板 :
容器是存放数据的类模板。被放入容器的对象所属的类最好重载“==”和“<”运算符
容器分为两大类:
- 顺序容器:位置同值无关,不排序的
- 可变长动态数组vector(内存连续存放)
- 双端队列deque(内存连续存放)
- 双向链表list(内存不连续存放)
- 关联容器:元素是排序的,默认情况下从小到大
- set
- multiset
- map
- multimap
- 顺序容器:位置同值无关,不排序的
容器适配器:
- 栈stack
- 队列queue
- 优先级队列priority_queue
容器都是类模板,实例化后成为容器类。vector
是一个容器类的名字,vector a就定义了一个容器对象 任何两个容器对象,只要类型相同,就可以用<,>,<=,>=,==,!=进行词典式的比较运算,返回一个布尔值
所有的容器都有以下两个成员函数:
- int size():返回容器对象中元素的个数
- bool empty():判断容器对象是否为空
顺序容器和关联容器还有以下的成员函数:
- begin():返回指向容器中第一个元素的迭代器
- end():返回指向容器中最后一个元素后面的位置的迭代器
- rbegin():返回指向容器中最后一个元素的反向迭代器
- rend():返回第一个元素前面的位置的反向迭代器
- erase():从容器中删除一个或几个元素
- clear():从容器中删除所有元素
顺序容器还有一下常用成员函数:
- front():返回容器中第一个元素的引用
- back():返回容器中最后一个元素的引用
- push_back():在容器末尾增加新元素
- pop_back():删除容器末尾的元素
- insert(…):插入一个或多个元素
迭代器 :
要访问顺序容器和关联容器中的元素,需要通过“迭代器”进行,迭代器与指针类似。
迭代器按照定义分为以下四种:
- 正向迭代器:容器类名::iterator 迭代器名;
- 常量正向迭代器:容器类名::const_iterator 迭代器名;
- 反向迭代器:容器类名::reverse_iterator 迭代器名;
- 常量反向迭代器:容器类名::const_reverse_iterator 迭代器名;
通过迭代器可以读取它指向的元素,“*迭代器名”就代表迭代器指向的元素。迭代器可以进行++,–操作
反向迭代器和正向迭代器的区别:对正向迭代器进行++操作时,得带起会指向容器的后一个元素;而对反向迭代器进行++操作时,迭代器会指向容器中的前一个元素。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using namespace std;
int main()
{
vector<int> v; //v是int类型的可变长数组,开始时没有元素
for(int n = 0;n < 5;++n)
{
v.push_back(n); //push_bask()成员函数在容器尾部添加一个元素
}
vector<int> :: iterator i; //定义正向迭代器
//用迭代器遍历元素
for(int i = v.begin();i != v.end();++i)
{
cout << *i << " "; //*i就是迭代器i指向的元素
*i *= 2; //每个元素变为原来的2倍
}
cout << endl;
//用反向迭代器遍历容器
for(vector<int> :: reverse_iterator i = v.rbegin();j != v.rend();++i)
{
cout << *j << " ";
}
return 0;
}
/*
程序输出的结果是:
0 1 2 3 4
8 6 4 2 0
*/容器适配器stack、queue、priority_queue没有迭代器
迭代器的功能分类:
- 正向迭代器:支持前置++,后置++,*操作。并且允许两个正向迭代器相互赋值,还可以用==,!=运算符进行比较
- 双向迭代器:具有正向迭代器的全部功能,并且前置–,后置–都是有定义的
- 随机访问迭代器:具有双向迭代器的全部功能,还支持+=,-=,+,-,p[i]等运算,并且还可以用<,>,<=,>=进行比较
不同容器的迭代器的功能:
容器 | 迭代器功能 |
---|---|
vector | 随机访问 |
deque | 随机访问 |
list | 双向 |
set、multiset | 双向 |
map、multimap | 双向 |
stack | 不支持迭代器 |
queue | 不支持迭代器 |
priority_queue | 不支持迭代器 |
例如vector的迭代器是随机访问迭代器,所以遍历vector容器有以下几种做法,下面的程序中,每个循环演示了一种做法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using namespace std;
int main()
{
vector<int> v(100); //v被初始化成有100个元素
for(int i = 0;i < v.size();++i) //size()函数返回元素个数
{
cout << v[i];
}
vector<int> :: iterator i;
for(i = v.begin();i != v.end();++i) //用!=比较两个迭代器
{
cout << *i;
}
for(i = v.begin();i < v.end();++i) //用<比较两个迭代器
{
cout << *i;
}
i = v.begin();
while(i < v.end())
{
cout << *i;
i += 2; //随机访问迭代器支持"+="整数的操作
}
return 0;
}迭代器的辅助函数:头文件algorithm
- advance(p,n):使迭代器p向前或向后移动n个元素
- distance(p,q):计算两个迭代器之间的距离,即迭代器p经过多少次++后才能与迭代器q相等
- iter_swap(p,q):用于交换两个迭代器p,q指向的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using namespace std;
int main()
{
int a[5] = {1,2,3,4,5};
list<int> lst(a,a + 5);
list<int> :: iterator p = lst.begin();
advance(p,2); //p向后两个元素,指向3
cout << "1)" << *p << endl;
advance(p,-1); //p向前一个元素,指向2
cout << "2)" << *p << endl;
list<int> :: itrator q = lst.end();
q--; //q指向5
cout << "3)" << distance(p,q) << endl;
iter_swap(p,q); //交换2和5
cout << "4)";
for(p = lst.begin(); p != lst.end();++p)
{
cout << *p << " ";
}
return 0;
}
/*
程序输出的结果是:
1) 3
2) 2
3) 3
4) 1 5 3 4 2
*/
算法 :
算法就是函数模板,通过迭代器来操纵容器中的元素。
- copy:将一个容器的内容复制到另一个容器中
- remove:在容器中删除一个元素
- random_shuffle:随机打乱容器中的元素
- fill:用某个值填充容器
- find:在容器中查找元素
- count_if:统计容器中符合某种条件的元素的个数
find功能:在区间[first,last)中按照顺序查找与某个变量或值相等的元素。如果找到则返回元素的迭代器;如果找不到,则返回last(不是end,而是end后面一个位置)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using namespace std;
int main()
{
int a[10] = {10,20,30,40};
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4); //在v中放4个元素1,2,3,4
vector<int> :: iterator p;
p = find(v.begin(),v.end(),3); //在v中找3
if(p != v.end()) //若找不到,find返回v.end()
{
cout << "1)" << *p << endl;
}
p = find(v.begin(),v.end(),9);
if(p == v.end())
{
cout << "not found" << endl;
}
p = find(v.begin() + 1,v.end() - 1,4) //在2,3两个元素中找4
cout << "2)" << *p << endl;
int* pp = find(a,a + 4,20);
if(pp == a + 4)
{
cout << "not found" << endl;
}
else
{
cout << "3)" << *pp << endl;
}
return 0;
}
/*
程序的输出结果是:
1) 3
not found
2) 4
3) 20
*/
顺序容器 :
动态数组vector :
头文件:vector
vector常见的成员函数:
- vector():无参构造函数,将容器初始化成空
- vector(int n):将容器初始化为有n个元素
- vector(int n,const T & val):将容器初始化为有n个元素,每个元素的值都是val
- vector(iterator first,iterator last):first和last可以是其他容器的迭代器。初始化的结果为将vector内容变为与其他容器上的区间[first,last)一致
- void clear():删除所有元素
- bool empty():判断容器是否为空
- void pop_back():删除容器末尾的元素
- void push_back(const T & val):将val添加到容器末尾、
- int size():返回容器中元素的个数
- T & front()/back():返回容器中第一个元素/最后一个元素的引用
- iterator insert(iterator i,const T & val):将val插入到迭代器i指向的位置,返回i
- iterator insert(iterator i,iterator first,iterator last):将其他容器上的区间[first,last)中的元素插入迭代器i指向的位置
- iterator erase(iterator i):删除迭代器i指向的元素,返回值是被删除的元素后面的元素的迭代器
- iterator erase(iterator first,iterator last):删除容器中区间[first,last)
- void swap(vector
& v):将容器自身的内容和一个同类型的容器v交换
双向队列deque :
头文件:deque
所有适用于vector的操作都适用于deque,此外还包括:
- void push_front(const T & val):将val插入到容器的头部
- void pop_back():删除容器头部的元素
随机存取任何元素都能在常数时间内完成
双向列表list :
头文件:list
在任何位置插入删除都是常数时间,不支持随机存取
除了具有顺序容器中都有的成员函数以外,还包括:
- void sort():将链表从小到大排序
- void remove(const T & v):删除和val大小相等的元素
- remove_if:删除符合某种条件的元素
- void unique():删除所有和前一个元素相等的元素
- void merge(list
& x):将链表x合并进来并清空x。要求链表自身和x都是有序的 - void splice(iterator i,list
& x,iterator first,iterator last):在位置i前面插入链表x中的区间[first,last),并在链表x中删除该区间。
STL中的算法sort可以用来对vector和deque排序,需要随机访问迭代器的支持。因为list不支持随机迭代访问器,所以不能用算法sort对list容器排序
函数对象 :
如果一个类将()运算符重载为成员函数,这个类就称为函数对象类,这个类的对象就是函数对象
()是目数不限的运算法,因此重载为成员函数时,有多少个参数都可以
关联容器 :
关联容器内部的元素是排好序的,有以下四种:
- set:排好序的集合,不允许有相同元素
- multiset:排好序的集合,允许有相同元素
- map:每个元素都分为关键字和值两部分,容器内的元素是按照关键字排序的,不允许有多个元素的关键字相同
- multimap:和map类似,差别在于元素的关键字可以相同
不能修改set和multiset,map和multimap容器中元素的值。因为元素被修改后,容器并不会自动重新调整顺序,于是容器的有序性就会被破坏。正确的做法是先删除该元素,再插入新元素。
在关联容器中查找元素和插入元素的时间复杂度都是O(log(n))
除了所有容器共有的成员函数外,关联容器还具有以下的成员函数:
- find:查找某个值
- lower_bound:查找某个下界
- upper_bound:查找某个上界
- equal_range:同时查找上界和下界
- count:计算等于某个值的元素个数
- insert:插入一个元素或一个区间
multiset :
使用multiset必须包含头文件set。multiset类模板定义如下:
1
2
3
4
5template <class Key,class Pred = less<key>,class B = allocator<key>>
class multiset
{
...
};multiset常用的成员函数如下:
- iterator find(const T & val):在容器中查找值为val的元素,返回其迭代器。如果找不到,返回end()
- iterator insert(const T & val):将val插入容器并返回其迭代器
- void insert(iterator first,iterator last):将区间[first,last)中的元素插入容器
- int count(const T & val):统计有多少个元素的值和val相等
- iterator lower_bound(const T & val):查找一个最大的位置it,使得[begin(),it)中的所有元素都比val小
- iterator upper_bound(const T & val):查找一个最小位置it,使得[it,end())中所有元素都比val大
- pair<iterator,iterator>equal_range(const T & val):同时求得lower_bound和upper_bound
- iterator erase(iterator it):删除it指向的元素,返回其后面的元素的迭代器
- iterator erase(iterator first,iterator last):删除区间[first,last),返回last
1 |
|
- 用erase成员函数删除迭代器i指向的元素后,迭代器i即告失效。++i后不能指向被删除元素的后面一个元素
set :
使用set文件必须包含头文件set,定义如下:
1
2
3
4
5template <class Key,class Pred = less<Key>,class A = allocator<key>>
class set
{
...
};set和multiset类似,差别在于set不能有重复的元素
insert函数的不同:pair<iterator,bool>insert(const T & val);返回值是pair模板类对象x,如果x.second为true,则说明插入成功,此时x.first指向被插入元素的迭代器;如果x.second为false,则说明要插入的元素已在容器中,x.first指向原有那个元素的迭代器
关联容器的equal_range成员函数的返回值也是pair模板类对象,其原型如下:pair<iterator,iterator>equal_range(const T & val);返回值对象中的first就是lower_bound的值,second就是upper_bound的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using namespace std;
int main()
{
typedef set<int>::iterator IT;
int a[5] = {3,4,6,1,2};
set<int> st(a,a + 5); //st中是1 2 3 4 6
pair<IT,bool> result;
result = st.insert(5); //st中是1 2 3 4 5 6
if(result.second) //插入成功则输出被插入元素
{
cout << *result.first << "inserted" << endl
}
//输出:5 inserted
if(st.insert(5).second)
{
cout << *result.first << endl;
}
else
{
cout << *result.first << "already exists" << endl;
}
//输出:5 already exists
pair<IT,IT> bounds = st.equal_range(4);
cout << *bound.first << "," << *bound.second; //输出4,5
return 0;
}
multimap :
使用multimap必须包含头文件map,定义如下:
1
2
3
4
5
6
7template<class Key,class T,class Pred = less<Key>,class A = allocator<T>>
class multimap
{
...
typedef pair(const Key,T) value_type;
...
};multimap中的元素都是pair模板类的对象。元素first成员变量也叫关键字,second成员变量也叫值。multimao容器中的元素按照关键字行销到达排序,默认情况下,元素的关键之间用less
比较大小,也就是<。multimao匀速多个元孙德关键字相同 multimap的成员函数如下:
- iterator find(const Key & val):在容器中查找关键字等于val的元素,返回其迭代器;如果找不到,返回end()
- iterator insert(pair<Key,T> const &p):将pair对象p插入容器中并返回其迭代器
- void insert(iterator first,iterator last):将区间[first,last)插入容器
- int count(const Key & val):统计有多少个元素的关键字和val相等
- iterator lower_bound(const Key & val):查找一个最大的位置it,使得[begin(),it)中的所有元素的关键字都比val小
- iterator upper_bound(const Key & val):查找一个最小的位置it,使得[it,end())中所有的元素的关键字都比val大
- pair<iterator,iterator>equal_range(const Key & val):同时求lower_bound和upper_bound
- iterator erase(iterator it):删除it指向的元素合格,返回其后面元素的迭代器
- iterator erase(iterator first,iterator last):删除区间[first,last)返回last
map :
- 要使用map必须包含头文件map,map的定义如下:
1
2
3
4
5
6
7template<class Key,class T,class Pred = less<Key>,class A = allocator<T>>
class map
{
...
typedef pair<const Key,T>value_type;
...
};
容器适配器 :
STL的容器适配器有stack,queue和priority_queue三种,都是在顺序容器的基础上实现的
容器适配器都有以下三个成员函数:
- push:添加一个元素
- top:返回顶部(对stack而言)或对头(对queue\priority_queue而言)的元素的引用
- pop:删除一个元素
容器适配器是没有迭代器的,因此STL中的各种排序、查找、变序等算法都不适用于容器适配器
stack :
头文件:stack。stack,“栈”,后进先出的元素序列,访问和删除都只能对栈顶的元素(最后一个被加入的元素)进行,元素也只能添加到栈顶。栈内元素不能访问
stack的定义如下:
1
2
3
4
5template<class T,class Cont = deque<T>>
class stack
{
...
};stack还有以下三个成员函数:
- void pop():弹出(即删除)栈顶元素
- T & top():返回栈顶元素的引用。通过此函数可以读取栈顶元素的值,也可以修改栈顶元素
- void push(const T & x):将x压入栈顶
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26//编写程序,输入一个十进制数n和进制k,输出n对应的k进制数
using namespace std;
int main()
{
int n,k;
stack<int> stk;
cin >> n >> k;
if(n == 0)
{
cout << 0;
return 0;
}
while(n)
{
stk.push(n % k);
n /= k;
}
while(!stk.empty())
{
cout << stk.top();
stk.pop();
}
return 0;
}
queue :
头文件:queue。queue,“队列”是先进先出的。队头的访问和删除操作只能在队头进行,添加操作只能在队尾进行。不能访问队列中间的元素。queue可以用list和deque实现
queue的定义如下:
1
2
3
4
5template<class T,class Cont = deque<T>>
class queue
{
...
};queue同样也有和stack类似的push,pop,top函数。区别在于queue的push发生在队尾,pop的top发生在队头
priority_queue :
- 头文件:queue。priority_queue,“优先队列”,优先队列的队头元素总是最大的,所以执行pop操作删除的总是最大的元素,top返回的总是最大元素的引用。默认的元素比较器是less
,默认为<
第十章作业 :
A:goodcopy :
描述
编写GoodCopy类模板,使得程序按指定方式输出
#include
using namespace std;
template
struct GoodCopy {
// 在此处补充你的代码
};
int a[200];
int b[200];
string c[200];
string d[200];
template
void Print(T s,T e) {
for(; s != e; ++s)
cout << * s << “,”;
cout << endl;
}
int main()
{
int t;
cin >> t;
while( t – ) {
int m ;
cin >> m;
for(int i = 0;i < m; ++i)
cin >> a[i];
GoodCopy
Print(b,b+m);
GoodCopy
Print(a+m/2,a+m/2 + m);
for(int i = 0;i < m; ++i)
cin >> c[i];
GoodCopy<string>()(c,c+m,d);
Print(c,c+m);
GoodCopy<string>()(c,c+m,c+m/2);
Print(c+m/2,c+m/2 + m);
}
return 0;
}
输入
第一行是整数 t,表示数据组数
每组数据:
第一行是整数 n , n < 50
第二行是 n 个整数
第三行是 n 个字符串
输出
将输入的整数原序输出两次,用”,”分隔
然后将输入的字符串原序输出两次,也用 “,”分隔
样例输入
2
4
1 2 3 4
Tom Jack Marry Peking
1
0
Ted
样例输出
1,2,3,4,
1,2,3,4,
Tom,Jack,Marry,Peking,
Tom,Jack,Marry,Peking,
0,
0,
Ted,
Ted,
1 |
|
B:按距离排序 :
描述
程序填空,输出指定结果
#include
#include
#include
#include
using namespace std;
template <class T1,class T2>
struct Closer {
// 在此处补充你的代码
};
int Distance1(int n1,int n2) {
return abs(n1-n2);
}
int Distance2(const string & s1, const string & s2)
{
return abs((int)s1.length()- (int) s2.length());
}
int a[10] = { 0,3,1,4,7,9,20,8,10,15};
string b[6] = {“American”,”Jack”,”To”,”Peking”,”abcdefghijklmnop”,”123456789”};
int main()
{
int n;string s;
while( cin >> n >> s ) {
sort(a,a+10,Closer<int ,int (*)(int ,int)> (n,Distance1));
for(int i = 0;i < 10; ++i)
cout << a[i] << “,” ;
cout << endl;
sort(b,b+6,Closer<string,int (*)(const string &,const string & )> (s,Distance2));
for(int i = 0;i < 6; ++i)
cout << b[i] << “,” ;
cout << endl;
}
return 0;
}
输入
多组数据,每组一行,是一个整数n和一个字符串s
输出
定义两个整数的距离为两个整数差的绝对值
定义两个字符串的距离为两个字符串长度差的绝对值
对每组数据:
对数组a按和n的距离从小到大排序后输出。距离相同的,值小的排在前面。
然后对数组b,按照和s的距离从小到大输出。距离相同的,字典序小的排在前面
样例输入
2 a123456
4 a12345
样例输出
1,3,0,4,7,8,9,10,15,20,
American,Peking,123456789,Jack,To,abcdefghijklmnop,
4,3,1,7,0,8,9,10,15,20,
Peking,American,Jack,123456789,To,abcdefghijklmnop,
1 |
|
C:很难蒙混过关的CArray3d三维数组模板类 :
描述
实现一个三维数组模版CArray3D,可以用来生成元素为任意类型变量的三维数组,输出指定结果
#include
#include
#include
using namespace std;
template
class CArray3D
{
// 在此处补充你的代码
};
CArray3D
CArray3D
void PrintA()
{
for(int i = 0;i < 3; ++i) {
cout << “layer “ << i << “:” << endl;
for(int j = 0; j < 4; ++j) {
for(int k = 0; k < 5; ++k)
cout << a[i][j][k] << “,” ;
cout << endl;
}
}
}
void PrintB()
{
for(int i = 0;i < 3; ++i) {
cout << “layer “ << i << “:” << endl;
for(int j = 0; j < 2; ++j) {
for(int k = 0; k < 2; ++k)
cout << b[i][j][k] << “,” ;
cout << endl;
}
}
}
int main()
{
int No = 0;
for( int i = 0; i < 3; ++ i ) {
a[i];
for( int j = 0; j < 4; ++j ) {
a[j][i];
for( int k = 0; k < 5; ++k )
a[i][j][k] = No ++;
a[j][i][i];
}
}
PrintA();
memset(a[1],-1 ,20*sizeof(int));
memset(a[1],-1 ,20*sizeof(int));
PrintA();
memset(a[1][1],0 ,5*sizeof(int));
PrintA();
for( int i = 0; i < 3; ++ i )
for( int j = 0; j < 2; ++j )
for( int k = 0; k < 2; ++k )
b[i][j][k] = 10.0/(i+j+k+1);
PrintB();
int n = a[0][1][2];
double f = b[0][1][1];
cout << "****" << endl;
cout << n << "," << f << endl;
return 0;
}
输入
无
输出
等同于样例
样例输入
无
样例输出
layer 0:
0,1,2,3,4,
5,6,7,8,9,
10,11,12,13,14,
15,16,17,18,19,
layer 1:
20,21,22,23,24,
25,26,27,28,29,
30,31,32,33,34,
35,36,37,38,39,
layer 2:
40,41,42,43,44,
45,46,47,48,49,
50,51,52,53,54,
55,56,57,58,59,
layer 0:
0,1,2,3,4,
5,6,7,8,9,
10,11,12,13,14,
15,16,17,18,19,
layer 1:
-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,
layer 2:
40,41,42,43,44,
45,46,47,48,49,
50,51,52,53,54,
55,56,57,58,59,
layer 0:
0,1,2,3,4,
5,6,7,8,9,
10,11,12,13,14,
15,16,17,18,19,
layer 1:
-1,-1,-1,-1,-1,
0,0,0,0,0,
-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,
layer 2:
40,41,42,43,44,
45,46,47,48,49,
50,51,52,53,54,
55,56,57,58,59,
layer 0:
10,5,
5,3.33333,
layer 1:
5,3.33333,
3.33333,2.5,
layer 2:
3.33333,2.5,
2.5,2,
7,3.33333
提示
建议做法:
- a[i][j][k] 这个表达式的第一个[]返回一个内部类的对象,该内部类也重载了[],且返回值为指针。
- 必要时需重载对象到指针的强制类型转换运算符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
using namespace std;
template <class T>
class CArray3D
{
public:
class CArray2D {
friend class CArray3D<T>;
private:
T* data;
int Y, Z;
public:
CArray2D() : data(nullptr), Y(0), Z(0) {}
void allocate(int y, int z) {
Y = y; Z = z;
data = new T[Y * Z]();
}
T* operator[](int y) {
return data + y * Z;
}
const T* operator[](int y) const {
return data + y * Z;
}
operator T*(){
return data;
}
~CArray2D() {
delete[] data;
}
};
private:
CArray2D* array2D;
int X;
public:
CArray3D(int x, int y, int z) : X(x) {
array2D = new CArray2D[X];
for (int i = 0; i < X; ++i) {
array2D[i].allocate(y, z);
}
}
CArray2D& operator[](int x) {
return array2D[x];
}
const CArray2D& operator[](int x) const {
return array2D[x];
}
~CArray3D() {
delete[] array2D;
}
};
CArray3D<int> a(3,4,5);
CArray3D<double> b(3,2,2);
void PrintA()
{
for(int i = 0;i < 3; ++i) {
cout << "layer " << i << ":" << endl;
for(int j = 0; j < 4; ++j) {
for(int k = 0; k < 5; ++k)
cout << a[i][j][k] << "," ;
cout << endl;
}
}
}
void PrintB()
{
for(int i = 0;i < 3; ++i) {
cout << "layer " << i << ":" << endl;
for(int j = 0; j < 2; ++j) {
for(int k = 0; k < 2; ++k)
cout << b[i][j][k] << "," ;
cout << endl;
}
}
}
int main()
{
int No = 0;
for( int i = 0; i < 3; ++ i ) {
a[i];
for( int j = 0; j < 4; ++j ) {
a[j][i];
for( int k = 0; k < 5; ++k )
a[i][j][k] = No ++;
a[j][i][i];
}
}
PrintA();
memset(a[1],-1 ,20*sizeof(int));
memset(a[1],-1 ,20*sizeof(int));
PrintA();
memset(a[1][1],0 ,5*sizeof(int));
PrintA();
for( int i = 0; i < 3; ++ i )
for( int j = 0; j < 2; ++j )
for( int k = 0; k < 2; ++k )
b[i][j][k] = 10.0/(i+j+k+1);
PrintB();
int n = a[0][1][2];
double f = b[0][1][1];
cout << "****" << endl;
cout << n << "," << f << endl;
return 0;
}
D:函数对象的过滤器 :
描述
程序填空输出指定结果
#include
#include
using namespace std;
struct A {
int v;
A() { }
A(int n):v(n) { };
bool operator<(const A & a) const {
return v < a.v;
}
};
// 在此处补充你的代码
template
void Print(T s,T e)
{
for(;s!=e; ++s)
cout << *s << “,”;
cout << endl;
}
template <class T1, class T2,class T3>
T2 Filter( T1 s,T1 e, T2 s2, T3 op)
{
for(;s != e; ++s) {
if( op(*s)) {
* s2 = * s;
++s2;
}
}
return s2;
}
ostream & operator <<(ostream & o,A & a)
{
o << a.v;
return o;
}
vector
vector aa;
int main()
{
int m,n;
while(cin >> m >> n)
{
ia.clear();
aa.clear();
int k,tmp;
cin >> k;
for(int i = 0;i < k; ++i) {
cin >> tmp;
ia.push_back(tmp);
aa.push_back(tmp);
}
vector
vector ab(k);
vector
Print(ib.begin(),p);
vector::iterator pp = Filter(aa.begin(),aa.end(),ab.begin(),FilterClass(m,n));
Print(ab.begin(),pp);
}
return 0;
}
输入
多组数据
每组数据两行
第一行是两个整数 m 和 n
第二行先是一个整数k ,然后后面跟着k个整数
输出
对每组数据,按原顺序输出第二行的后k个整数中,大于m且小于n的数
输出两遍
数据保证一定能找到符合要求的整数
样例输入
1 3
1 2
2 8
5 1 2 3 4 9
样例输出
2,
2,
3,4,
3,4,
1 |
|
E:白给的list排序 :
描述
程序填空,产生指定输出
#include
#include
#include
#include
using namespace std;
int main()
{
double a[] = {1.2,3.4,9.8,7.3,2.6};
list
lst.sort(
// 在此处补充你的代码
);
for(list
cout << * i << “,” ;
return 0;
}
输入
无
输出
9.8,7.3,3.4,2.6,1.2,
样例输入
无
样例输出
同输入
1 |
|
F:我自己的 ostream_iterator :
描述
程序填空输出指定结果
#include
#include
#include
using namespace std;
template <class T1,class T2>
void Copy(T1 s,T1 e, T2 x)
{
for(; s != e; ++s,++x)
*x = *s;
}
template
class myostream_iteraotr
{
// 在此处补充你的代码
};
int main()
{ const int SIZE = 5;
int a[SIZE] = {5,21,14,2,3};
double b[SIZE] = { 1.4, 5.56,3.2,98.3,3.3};
list
myostream_iteraotr
Copy( lst.begin(),lst.end(),output);
cout << endl;
myostream_iteraotr
Copy(b,b+SIZE,output2);
return 0;
}
输入
无
输出
5,21,14,2,3,
1.4–5.56–3.2–98.3–3.3–
样例输入
无
样例输出
同输入
1 |
|
STL专项练习8选6 :
A:List :
描述
写一个程序完成以下命令:
new id ——新建一个指定编号为id的序列(id < 10000)
add id num——向编号为id的序列加入整数
num merge id1 id2——如果id1等于id2,不做任何事,否则归并序列id1和id2中的数,并将id2清空
unique id——去掉序列id中重复的元素
out id ——从小到大输出编号为id的序列中的元素,以空格隔开
输入
第一行一个数n,表示有多少个命令( n<=200000)。以后n行每行一个命令。
输出
按题目要求输出。
样例输入
16
new 1
new 2
add 1 1
add 1 2
add 1 3
add 2 1
add 2 2
add 2 3
add 2 4
out 1
out 2
merge 1 2
out 1
out 2
unique 1
out 1
样例输出
1 2 3
1 2 3 4
1 1 2 2 3 3 4
1 2 3 4
1 |
|
C:Set :
描述
现有一整数集(允许有重复元素),初始为空。我们定义如下操作:
add x 把x加入集合
del x 把集合中所有与x相等的元素删除
ask x 对集合中元素x的情况询问
对每种操作,我们要求进行如下输出。
add 输出操作后集合中x的个数
del 输出操作前集合中x的个数
ask 先输出0或1表示x是否曾被加入集合(0表示不曾加入),再输出当前集合中x的个数,中间用空格格开。
输入
第一行是一个整数n,表示命令数。0<=n<=100000。
后面n行命令,如Description中所述。
输出
共n行,每行按要求输出。
样例输入
7
add 1
add 1
ask 1
ask 2
del 2
del 1
ask 1
样例输出
1
2
1 2
0 0
0
2
1 0
提示
Please use STL’s set and multiset to finish the task
1 |
|
E:热血格斗场 :
描述
为了迎接08年的奥运会,让大家更加了解各种格斗运动,facer新开了一家热血格斗场。格斗场实行会员制,但是新来的会员不需要交入会费,而只要同一名老会员打一场表演赛,证明自己的实力。
我们假设格斗的实力可以用一个正整数表示,成为实力值。另外,每个人都有一个唯一的id,也是一个正整数。为了使得比赛更好看,每一个新队员都会选择与他实力最为接近的人比赛,即比赛双方的实力值之差的绝对值越小越好,如果有两个人的实力值与他差别相同,则他会选择比他弱的那个(显然,虐人必被虐好)。
不幸的是,Facer一不小心把比赛记录弄丢了,但是他还保留着会员的注册记录。现在请你帮facer恢复比赛纪录,按照时间顺序依次输出每场比赛双方的id。
输入
第一行一个数n(0 < n <=100000),表示格斗场新来的会员数(不包括facer)。以后n行每一行两个数,按照入会的时间给出会员的id和实力值。一开始,facer就算是会员,id为1,实力值1000000000。输入保证两人的实力值不同。
输出
N行,每行两个数,为每场比赛双方的id,新手的id写在前面。
样例输入
3
2 1
3 3
4 2
样例输出
2 1
3 2
4 2
1 |
|
F:冷血格斗场 :
描述
为了迎接08年的奥运会,让大家更加了解各种格斗运动,facer新开了一家冷血格斗场。格斗场实行会员制,但是新来的会员不需要交入会费,而只要同一名老会员打一场表演赛,证明自己的实力。
我们假设格斗的实力可以用一个非负整数表示,称为实力值,两人的实力值可以相同。另外,每个人都有一个唯一的id,也是一个正整数。为了使得比赛更好看,每一个新队员都会选择与他实力最为接近的人比赛,即比赛双方的实力值之差的绝对值越小越好,如果有多个人的实力值与他差别相同,则他会选择id最小的那个。
不幸的是,Facer一不小心把比赛记录弄丢了,但是他还保留着会员的注册记录。现在请你帮facer恢复比赛纪录,按照时间顺序依次输出每场比赛双方的id。
输入
第一行一个数n(0 < n <=100000),表示格斗场新来的会员数(不包括facer)。以后n行每一行两个数,按照入会的时间给出会员的id和实力值。一开始,facer就算是会员,id为1,实力值1000000000。
输出
N行,每行两个数,为每场比赛双方的id,新手的id写在前面。
样例输入
3
2 3
3 1
4 2
样例输出
2 1
3 2
4 2
1 |
|
G:priority queue练习题 :
描述
我们定义一个正整数a比正整数b优先的含义是:
*a的质因数数目(不包括自身)比b的质因数数目多;
当两者质因数数目相等时,数值较大者优先级高。
现在给定一个容器,初始元素数目为0,之后每次往里面添加10个元素,每次添加之后,要求输出优先级最高与最低的元素,并把该两元素从容器中删除。
输入
第一行: num (添加元素次数,num <= 30)
下面10num行,每行一个正整数n(n < 10000000).
输出
每次输入10个整数后,输出容器中优先级最高与最低的元素,两者用空格间隔。
样例输入
1
10 7 66 4 5 30 91 100 8 9
样例输出
66 5
1 |
|
H:编程填空:数据库内的学生信息 :
描述
程序填空,使得下面的程序,先输出
(Tom,80),(Tom,70),(Jone,90),(Jack,70),(Alice,100),
(Tom,78),(Tom,78),(Jone,90),(Jack,70),(Alice,100),
(70,Jack),(70,Tom),(80,Tom),(90,Jone),(100,Alice),
(70,Error),(70,Error),(80,Tom),(90,Jone),(100,Alice),
然后,再根据输入数据按要求产生输出数据
#include
#include
#include
Student s[] = { {"Tom",80},{"Jack",70},
{"Jone",90},{"Tom",70},{"Alice",100} };
MyMultimap<string,int> mp;
for(int i = 0; i<5; ++ i)
mp.insert(make_pair(s[i].name,s[i].score));
Print(mp.begin(),mp.end()); //按姓名从大到小输出
mp.Set("Tom",78); //把所有名为"Tom"的学生的成绩都设置为78
Print(mp.begin(),mp.end());
MyMultimap<int,string,less<int> > mp2;
for(int i = 0; i<5; ++ i)
mp2.insert(make_pair(s[i].score,s[i].name));
Print(mp2.begin(),mp2.end()); //按成绩从小到大输出
mp2.Set(70,"Error"); //把所有成绩为70的学生,名字都改为"Error"
Print(mp2.begin(),mp2.end());
cout << "******" << endl;
mp.clear();
string name;
string cmd;
int score;
while(cin >> cmd ) {
if( cmd == "A") {
cin >> name >> score;
if(mp.find(name) != mp.end() ) {
cout << "erroe" << endl;
}
mp.insert(make_pair(name,score));
}
else if(cmd == "Q") {
cin >> name;
MyMultimap<string,int>::iterator p = mp.find(name);
if( p!= mp.end()) {
cout << p->second << endl;
}
else {
cout << "Not Found" << endl;
}
}
}
return 0;
}
输入
输入数据的每一行,格式为以下之一:
A name score
Q name score
name是个不带个空格的字符串,长度小于 20
score是个整数,能用int表示
A name score 表示往数据库中新增一个姓名为name的学生,其分数为score。开始时数据库中一个学生也没有。
Q name 表示在数据库中查询姓名为name的学生的分数
数据保证学生不重名。
输入数据少于200,000行。
输出
对于每个查询,输出学生的分数。如果查不到,则输出 “Not Found”
样例输入
A Tom1 30
A Tom2 40
Q Tom3
A Tom4 89
Q Tom1
Q Tom2
样例输出
(Tom,80),(Tom,70),(Jone,90),(Jack,70),(Alice,100),
(Tom,78),(Tom,78),(Jone,90),(Jack,70),(Alice,100),
(70,Jack),(70,Tom),(80,Tom),(90,Jone),(100,Alice),
(70,Error),(70,Error),(80,Tom),(90,Jone),(100,Alice),
Not Found
30
40
提示
编写模板的时候,连续的两个 “>”最好要用空格分开,以免被编译器看作是 “>>”运算符。VS可能无此问题,但是Dev C++和服务器上的编译环境会有这个问题。
比如 vector<vector> 有可能出错,要改成 vector<vector > 在模板中写迭代器时,最好在前面加上 typename关键字,否则可能会编译错。VS可能无此问题,但是Dev C++和服务器上的编译环境会有这个问题。
1 |
|