文章目录
-
引用 & const
1.常引用与非常引用的转换const T & 和 T & 是不同的类型;
2.T &类型的引用或T类型的变量可以用来初始化const T &类型的引用;
3.const T类型的常变量或const T&类型的引用则不能用来初始化T & 类型的引用,除非进行强制类型转换;【总结:常引用 可用 非常引用来初始化,反过来则不行】
4.不能把常量指针赋值给非常量指针,反过来可以
const int* p1; int* p2; p1 = p2;//ok p2 = p1//error p2 = (int*)p1;//ok
5.函数参数为常量指针时,可避免函数内部不小心改变参数指针所指地方的内容
6.常引用不能通过引用去修改其引用的变量的内容int n ; const int &r = n; r = 5;//error n = 4;//ok
7.引用在定义的时候就必须初始化,不能够建立void型引用、不能建立引用的数组、没有多级引用、
const char* pc = "abcd";//指向常量的指针 pc[3] = 'x';//error,不允许改变指针所指的常量 pc = "efgh";//由于不是常指针,故可以改变指针所指的地址 char* const pc = "abcd";//常指针,就是一个固定的不能够移动的指针,不能够改变指针所指的地址 pc[3] = 'x';//ok,可以修改指针所指地址中的数据 pc = "efgh";//error,不能修改指针所指的地址 const char* const pc = "abcd";//指向常量的常指针,即所指的地址以及地址里面的内容都不能够修改
-
new & delete
1.分配一个变量:P = new T,T是任意类型名,P是类型为T*的指针,动态分配出一片大小为sizeof(T)的内容空间,并将该内存空间的起始地址赋值给P
int* p; p = new int; *p = 5;
2.分配一个数组:P = new T[N],动态分配出一块大小为N * sizeof(T)的内存空间
3.delete p 或 delete []p
-
内联函数
为了减少函数调用开销,编译器处理对内联函数的常用语句时是将整个代码块插入到调用函数语句处【注:不能有复杂的控制语句如for、switch等,不做语法检查仅仅是简单的置换】
-
函数重载
一个或多个函数,名字相同,然而参数的个数或类型不同
-
函数的缺省
定义函数的时候可以让右边的连续若干个参数有缺省值(默认值),那么函数调用时,若相应的位置不写参数,参数就是缺省值。【注:一旦开始使用缺省值比剩下右边的参数都必须使用缺省值】
-
面向对象的程序设计具有
抽象、封装、继承、多态的四个基本特点
-
类
类的名字就是用户自定义的类型的名字,可以像使用基本类型那样使用它;类定义出来的变量也成为对象;对象所占的存储空间为所有成员变量的大小之和(每个对象之间相互独立);对象之间可以使用 “=” 进行赋值,但是不能使用其他运算符,除非进行了运算符重载
-
使用类的成员变量和成员函数
法一:对象名.成员名
法二:指针->成员名CR r1, r2; CR* p1 = & r1;p1->w = 5;
法三:引用名.成员名
CR r1; CR &rr = r1; rr.w = 5;
类的成员函数和类分开写 T 类名::成员函数名
-
类成员的可访问范围
private(默认;仅在成员函数内访问)、public(任何地方)、protect()
-
第一周作业
选择:DCBBAD
//难一点的swap #include <iostream> using namespace std; void swap(int* (&a), int* (&b)) { int * tmp = a; cout << tmp << ","; a = b; cout << b << ","; b = tmp; cout << b << ","; } int main() { int a = 3,b = 5; int * pa = & a; int * pb = & b; swap(pa,pb); cout << *pa << "," << *pb; system("pause"); return 0; }
分析:此处如果单纯的只传入
void swap(int *a,int *b)
那么我们会发现其实两个值在输出时,并没有发生交换,这是因为在子函数中,如果我们不改变指针指向的值,那么即使改变指针的指向也不会影响原函数得值。因此,此处最好将主函数中传过来得地址进行强行转换,如void swap(int *(&a),int *(&b))
,那么a和b就是pa和pb的地址了,相当于就是pa和pb,完全一样,可以进行交换指针了。//神秘的数组初始化 #include <iostream> using namespace std; int main(){ int * a[] = {NULL,NULL,new int[1],new int[7]}; *a[2] = 123; a[3][5] = 456; if(! a[0] ) { cout << * a[2] << "," << a[3][5]; } system("pause"); return 0; }
分析:首先确定a是个指针数组,里面的元素都是指针或者地址。然后看到 *a[2],那么a[2]肯定是一个指针或者内存空间。既然要输入一个值,那我们就给a[2]分配一个int空间。再然后看到a[3][5],因此我们刚开始认为a是个二维指针数组,但是那样写起来太麻烦了。于是我们把a[3]看成a中一段连续的空间,其中的第5个元素需要赋值,所以至少需要分配内存给它。所以我分配了6个int型的空间给a[3],问题解决。注意,从这个问题可以学到,在数组初始化时的大括号里,我们可以用到new函数,答案不唯一。
-
构造函数
成员函数的一种,函数名与类名相同,可以有参数,可以重载,但是不能有返回值(viod也不行);作用是对对象进行初始化,若没有定义构造函数则系统会生成一个默认的构造函数,默认构造函数无参数,不做任何操作(若自己定义了构造函数则系统不会再生成默认构造函数)
//构造函数在数组中的使用 class test { public: test(int n) {}; //1 test(int n, int m) {}; //2 test() {}; //3 }; test array1[3] = {1, test(1, 2)}; //分别用1 2 3初始化 test array2[3] = {test(1, 2), test(3, 4), 1}; //分别使用2 2 1进行初始化 test *parray[3] = {new test(1), new test(2, 3)}; //只生成两个对象分别用1 2 进行初始化
-
复制构造函数
只有一个参数即同类对象的引用,T::T(T&)或T::T(const T&)若无定义则系统自动生成一个默认复制构造函数,完成复制功能(若自己定义了则默认的就不复存在);不允许形如T::T(T)这样的构造函数;
//当一个对象去初始化同类的另一个对象时 T t2(t1);//调用 T t2 = t1;//初始化语句,非赋值语句,调用 //如果某个函数的参数是T的一个对象,那么当该函数被调用时,类T 的复制构造函数将被调用 void Func(A a1){ } int main(){ A a2; Func(a2);//函数调用将会产生复制函数的调用 return 0; } //若函数的返回值是类T 的对象时,则函数返回时复制构造函数将被调用 A Func() { A b(4); return b;//b是一个对象 } int main() { cout << Func().v << endl; return 0; } /* 输出结果: Copy constructor called 4 */ //注:对象间的赋值并不会导致赋值构造函数被调用 T t1, t2; t1.n = 5; t2 = t1;//不调用 T t3(t1);//调用
-
类型转换构造函数
定义转换构造函数的目的是实现类型的自动转换。只有一个参数,而且不是复制构造函数的构造函数,一般就可以看作是转换构造函数。当需要的时候,编译系统会自动调用转换构造函数,建立一个无名的临时对象(或临时变量)。
lass Complex { public: double real, imag; Complex( int i) {//类型转换构造函数 cout << "IntConstructor called" << endl; real = i; imag = 0; } Complex(double r,double i) {real = r; imag = i; } }; int main () { Complex c1(7,8); Complex c2 = 12; c1 = 9; // 9被自动转换成一个临时Complex对象 cout << c1.real << "," << c1.imag << endl;//real = 9, imag = 0 return 0; }
-
析构函数
名字与类名相同,在前面加‘~’,没有参数和返回值,一个类最多只能有一个析构函数。析构函数对象消亡时即自动被调用。可以定义析构函数来在对象消亡前做善后工作,比如释放分配的空间等。如果定义类时没写析构函数,则编译器生成缺省析构函数。缺省析构函数什么也不做。如果定义了析构函数,则编译器不生成缺省析构函数。【若new一个对象数组,那么用delete释放时应该写 [ ]。否则只delete一个对象(调用一次析构函数) 】
-
第二周作业
选择:A2CC3
//学生信息处理系统 #include <iostream> #include <string> #include <cstdio> #include <cstring> #include <sstream> #include <cstdlib> using namespace std; class Student { // 在此处补充你的代码 char name[20]; int id; int age; int score[4]; double average; char c; public: void input() { cin.getline(name, 20, ','); //cin.getline(字符指针(char*),字符个数N(int),结束符(char)); //一次读取多个字符(包括空白字符),直到读满N-1个,或者遇到指定的结束符为止(默认的是以'\n'结束) cin >> age >> c >> id; for (int i = 0; i < 4; i++) { cin >> c >> score[i]; } } void calculate() { int sum = 0; for (int i = 0; i < 4; i++) { sum += score[i]; } average = sum / 4.0; } void output() { cout << name << "," << age << "," << id << "," << average << endl; } }; int main() { Student student; // 定义类的对象 student.input(); // 输入数据 student.calculate(); // 计算平均成绩 student.output(); // 输出数据 system("pause"); return 0; }
//奇怪的类复制 #include <iostream> using namespace std; class Sample { public: int v; // 在此处补充你的代码 Sample(int i = 0) {// 类型转换构造函数 v = i; cout << "-----" << endl; } Sample(const Sample &s) {// 复制构造函数 v = s.v + 2; cout << "+++++" << endl; } }; void PrintAndDouble(Sample o) { cout << o.v; cout << endl; } int main() { Sample a(5); // 使用含一个int型的参数对此对象进行初始化 Sample b = a; // 并不是一个赋值语句,而是利用对象a对b进行初始化,此时需要一个复制构造函数来使用对象a中的参数对b进行初始化 PrintAndDouble(b); // 使用函数PrintAndDouble( )时,函数使用的形参是一个对象,输出的结果是对象的一个参数,那么就需要一个复制构造函数来对此对象进行初始化 Sample c = 20; // 调用类型转换构造函数 PrintAndDouble(c); Sample d; // 定义了一个对象而未对其进行初始化,那么就会自动调用一个默认的构造函数来对其进行初始化,因为已经定义了其他有参数的构造函数,那么定义一个无参的空的默认构造函数是不可省略的(或者使用默认参数值) d = a; // 对象d是由对象a对其赋值得到的,对象间的赋值并不会导致赋值构造函数被调用,,在程序执行过程中也并未对a的参数进行修改,所以d.v应与a.v一致 cout << d.v; system("pause"); return 0; }
//超简单的复制类 #include <iostream> #include <cstring> #include <cstdlib> using namespace std; class Complex { private: double r,i; public: Complex() {} Complex (char c[]) { r = c[0] - '0'; i = c[2] - '0'; } void Print() { cout << r << "+" << i << "i" << endl; } }; int main() { Complex a; // 需要无参构造函数 a = "3+4i"; // 需要类型转换构造函数 a.Print(); a = "5+6i"; a.Print(); return 0; }
//哪来的输出 #include <iostream> using namespace std; class A { public: int i; A(int x) { i = x; } // 在此处补充你的代码 ~A() { cout << i << endl; } }; int main() { A a(1); A * pa = new A(2); delete pa; // 析构pa,输出2 system("pause"); return 0; // 程序结束,析构a 输出1 }
int main() { A * p = new A[2]; A * p2 = new A; A a; delete [] p; } //这段程序共调用3次析构函数 // a 到这里的时候,它占用的内存会被释放,而p2,除非调用 delete p2,否则内存永远不会被回收,指针p2丢弃后,那块内存没被释放,无法被再次使用,造成内存浪费。
-
this 指针
其作用就是指向成员函数所作用的对象
1.非静态成员函数中可以直接使用this来代表指向该函数作用的对象的指针(return *this)2.静态成员函数中不能使用this指针(因为静态成员函数并不具体作用于某个对象,因此静态成员函数的真实的参数的个数就是程序中写出的参数的个数)
-
static 关键字
1.静态成员(实际上就好像有一个全局变量一样):在成员的说明前加上static关键字的成员(普通成员变量就每个对象各自拥有一份,而静态成员变量一共就一份,为所有对象所共享,并不具体作用于某个对象;sizeof不会计算静态成员变量)
2.静态成员函数(本质上是全局函数):普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用于某个对象
3.静态成员/函数不需要通过对象就能访问
类名::成员名;
类名 . 成员名(虽然这样访问,但是它并不只属于这个对象)
指针->成员名
引用 . 成员名4.设置静态成员这种机制的目的是将和某些类紧密相关的全局变量和函数写到类里面,看上去像一个整体,易于维护和理解
5.必须在定义类的文件中对静态成员变量进行一次说明或初始化。否则编译能通过,链接不能通过。
6.在静态成员函数中不能访问非静态成员变量,也不能调用非静态成员函数(because 解释不清非静态成员属于哪个对象的)
-
成员对象和封闭(enclosing)类
有成员对象的类叫封闭类
1.通过封闭类的构造函数的初始化列表来初始化成员对象(封闭类对象生成时,先执行所有对象成员的构造函数,然后才执行封闭类的构造函数;对象成员的构造函数调用次序和对象成员在类中的说明次序一致,与它们在成员初始化列表中出现的次序无关)
2.当封闭类的对象消亡时,先执行封闭类的析构函数,然后再执行成员对象的析构函数。次序和构造函数的调用次序相反(类似stack)
3.封闭类的对象,如果是用默认复制构造函数初始化的,那么它里面包含的成员对象,也会用复制构造函数初始化
-
友元(friend关键字)
友元函数和友元类(友元类之间的关系不能传递,不能继承)
1.友元函数: 一个类的友元函数可以访问该类的私有成员(可以将一个类的成员函数(包括构造、析构函数)说明为另一个类的友元)
2.友元类: 如果A是B的友元类,那么A的成员函数可以访问B的私有成员
-
常量成员函数
如果不希望某个对象的值被改变,则定义该对象的时候可以在前面加 const关键字(在类的成员函数说明后面可以加const关键字,则该成员函数成为常量成员函数。常量成员函数内部不能改变属性的值,也不能调用非常量成员函数)
-
1.在定义常量成员函数和声明常量成员函数时都应该使用const 关键字
2.如果一个成员函数中没有调用非常量成员函数,也没有修改成员变量的值,那么,最好将其写成常量成员函数
3.两个函数,名字和参数表都一样,但是一个是const,一个不是,算重载
4.mutable成员变量可以在const成员函数中修改的成员变量
-
第三周作业
选择:BAAB
//返回什么才好呢 #include <iostream> using namespace std; class A { public: int val; A(int n = 123) { val = n; } // 在此处补充你的代码 A &GetObj() { return *this; // 返回左值尽量是 this指针指向的对象 } }; 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; }
//Big & Base封装类 #include <iostream> #include <string> using namespace std; class Base { public: int k; Base(int n):k(n) { } }; class Big { public: int v; Base b; // 考擦初始化列表 Big (int n) : v(n), b(n) {} }; 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; } }
//统计动物数量 #include <iostream> using namespace std; // 在此处补充你的代码 class Animal { public: static int number; Animal() { number++; } virtual ~Animal() {//如果不加virtual,删除c2的时候就不能调用Cat的析构函数了 number--; } }; class Cat:public Animal { public: static int number; Cat() { number++; } ~Cat() { number--; } }; class Dog:public Animal { public: static int number; Dog() { number++; } ~Dog() { number--; } }; void print() { cout << Animal::number << " animals in the zoo, " << Dog::number << " of them are dogs, " << Cat::number << " of them are cats" << endl; } int Animal::number = 0; int Dog::number = 0; int Cat::number = 0; int main() { print(); Dog d1, d2; Cat c1; print(); Dog* d3 = new Dog(); Animal* c2 = new Cat; Cat* c3 = new Cat; print(); delete c3; delete c2; delete d3; print(); }
分析:为什么父类的析构函数不定义成虚函数,删除c2的时候就不会调用~Cat()了?而为什么即使把父类的析构函数定义成虚函数,调用子类的析构函数后仍然会调用父类的虚函数?
析构函数执行时先调用派生类的析构函数,其次才调用基类的析构函数。如果析构函数不是虚函数,而程序执行时又要通过基类的指针去销毁派生类的动态对象,那么用delete销毁对象时,只调用了基类的析构函数,未调用派生类的析构函数。这样会造成销毁对象不完全。(感谢:https://blog.csdn.net/lily_prince/article/details/80509426)//这个指针哪里来的 #include <iostream> using namespace std; struct A { int v; A(int vv):v(vv) { } // 在此处补充你的代码 const A* getPointer() const { return this; } }; int main() { const A a(10); const A * p = a.getPointer(); cout << p->v << endl; return 0; }
分析:p的类型是const A *,getPointer()是成员函数,getPointer()的返回值是const A *类型,这种方式我们容易联想到this指针。同时,a的类型我们也不能修改。
#include <iostream> using namespace std; int tm = 0; bool flagRS = true, flagBS = true;//红军与蓝军停止标志 class dragon { public: int total = 0; static int strength; }; class wolf { public: int total = 0; static int strength; }; class ninja{ public: int total = 0; static int strength; }; class iceman { public: int total = 0; static int strength; }; class lion { public: int total = 0; static int strength; }; class Red { int number; dragon dg; ninja nj; iceman ice; lion li; wolf wo; public: int M; Red(int M_, int number_ = 0); void born(int typeR); void stop(); void print(const char *s, int s1, int t1); }; Red::Red(int M_, int number_) :M(M_),number(number_) {} void Red::born(int typeR) {//根据武士种类制造 number++; switch (typeR) { case 1:ice.total++; M -= ice.strength; print("iceman", ice.strength, ice.total); break; case 2:li.total++; M -= li.strength; print("lion", li.strength, li.total); break; case 3:wo.total++; M -= wo.strength; print("wolf", wo.strength, wo.total); break; case 4:nj.total++; M -= nj.strength; print("ninja", nj.strength, nj.total); break; case 5:dg.total++; M -= dg.strength; print("dragon", dg.strength, dg.total); break; } } void Red::print(const char *s, int s1, int t1) {//输出 printf("%03d red %s %d born with strength %d,%d %s in red headquarter\n", tm, s, number, s1, t1, s); } void Red::stop() {//停止制造 flagRS = false; printf("%03d red headquarter stops making warriors\n", tm); } class Blue {//同上 int number; dragon dg; ninja nj; iceman ice; lion li; wolf wo; public: int M; Blue(int M_, int number_ = 0); void born(int typeB); void stop(); void print(const char *s, int s1, int t1); }; Blue::Blue(int M_, int number_) :M(M_), number(number_) {} void Blue::born(int typeB) { number++; switch (typeB) { case 1: li.total++; M -= li.strength; print("lion", li.strength, li.total); break; case 2:dg.total++; M -= dg.strength; print("dragon", dg.strength, dg.total); break; case 3:nj.total++; M -= nj.strength; print("ninja", nj.strength, nj.total); break; case 4:ice.total++; M -= ice.strength; print("iceman", ice.strength, ice.total); break; case 5:wo.total++; M -= wo.strength; print("wolf", wo.strength, wo.total); break; } } void Blue::print(const char *s, int s1, int t1) { printf("%03d blue %s %d born with strength %d,%d %s in blue headquarter\n", tm, s, number, s1, t1, s); } void Blue::stop() { flagBS = false; printf("%03d blue headquarter stops making warriors\n", tm); } //千万不能忘 int dragon::strength; int ninja::strength; int iceman::strength; int lion::strength; int wolf::strength; int main() { int n, M, group = 1; cin >> n; while (n > 0) { cin >> M; cin >> dragon::strength >> ninja::strength >> iceman::strength >> lion::strength >> wolf::strength; int r[6] = { 0, iceman::strength, lion::strength, wolf::strength, ninja::strength, dragon::strength }; int b[6] = { 0, lion::strength, dragon::strength, ninja::strength, iceman::strength,wolf::strength }; int min = r[1]; for (int i = 2; i < 6; i++) {//武士中最小生命值 if (r[i] < min)min = r[i]; } Red *red = new Red(M); Blue *blue = new Blue(M); cout << "Case:" << group << endl; if (M < min) { red->stop(); blue->stop(); } int i = 1, j = 1; while (flagBS || flagRS) { if (flagRS) { while (r[i] > red->M) { i++; if (i > 5) i = 1; } red->born(i); i++; if (i > 5) i = 1; } if (blue->M < min && flagBS) blue->stop(); if (flagBS) { while (b[j] > blue->M) { j++; if (j > 5) j = 1; } blue->born(j); j++; if (j > 5) j = 1; } tm++;//时间加一 if (red->M < min && flagRS) red->stop(); } delete red; delete blue; flagBS = flagRS = true;//重置 tm = 0;//时间重置 group++; n--; } system("pause"); return 0; }
魔兽世界备战一:没写,参考大佬的博客
-
运算符重载
实质就是函数的重载(既可以重载普通函数,也可以重载为成员函数)
1.把含运算符的表达式转换为对运算符函数的调用
2.把运算符的操作数转换为运算符函数的参数
3.运算符重载多次的时候根据实参类型决定调用哪个运算函数返回值类型 operator 运算符 (形参表) { }
4.重载为成员函数时,参数个数为运算符数目减一
5.重载为普通函数时,参数个数为运算符数目class Complex { public: double real,imag; Complex( double r = 0.0, double i= 0.0 ):real(r),imag(i) { } Complex operator-(const Complex & c); }; Complex operator+( const Complex & a, const Complex & b) { return Complex( a.real+b.real,a.imag+b.imag); //返回一个临时对象 } Complex Complex::operator-(const Complex & c) { return Complex(real - c.real, imag - c.imag); //返回一个临时对象 } int main() { Complex a(4,4),b(1,1),c; c = a + b; //等价于c=operator+(a,b); cout << c.real << "," << c.imag << endl; cout << (a-b).real << "," << (a-b).imag << endl; //a-b等价于a.operator-(b) return 0; } 输出: 5,5 3,3 c = a + b; 等价于c=operator+(a,b); a-b 等价于a.operator-(b)
6.如果将 [ ] 运算符重载成一个类的成员函数,则该重载函数有几个参数?答:一个,因为[ ]为双目运算符
-
“=”赋值运算符的重载: 只能重载为成员函数
class String { private: char * str; public: String ():str(new char[1]) { str[0] = 0;} const char * c_str() { return str; }; String & operator = (const char * s); String::~String( ) { delete [] str; } }; String & String::operator = (const char * s) { //重载“=”以使得 obj = “hello”能够成立 delete [] str; str = new char[strlen(s)+1]; strcpy( str, s); return * this; } int main() { String s; s = "Good Luck," ; //等价于 s.operator=("Good Luck,"); cout << s.c_str() << endl; // String s2 = "hello!"; //这条语句要是不注释掉就会出错,因为这应是初始化 s = "Shenzhou 8!"; //等价于 s.operator=("Shenzhou 8!"); cout << s.c_str() << endl; return 0; } 输出: Good Luck, Shenzhou 8!
-
深拷贝 & 浅拷贝
1.浅拷贝
分析: 如不定义自己的赋值运算符,那么S1=S2实际上导致 S1.str和 S2.str
指向同一地方。
1)如果S1对象消亡,析构函数将释放 S1.str指向的空间,则S2消亡时还
要释放一次,不妥。
2)另外,如果执行 S1 = “other”;会导致S2.str指向的地方被delete 因此要在 class String里添加成员函数:String & operator = (const String & s) {//重载赋值运算符 if( this == & s) return * this; delete [] str; str = new char[strlen( s.str)+1]; strcpy( str,s.str); return * this; }
对于operator = 返回值的讨论
void 好不好?
String 好不好?
为什么是 String &
对运算符进行重载的时候,好的风格是应该尽量保留运算符原本的特性考虑: a = b = c; 和 (a=b)=c; //会修改a的值 实际上 = 的左值是一个引用 分别等价于: a.operator=(b.operator=(c)); (a.operator=(b)).operator=(c); 上面的String类是否就没有问题了? 为 String类编写复制构造函数的时候,会面临和 = 同样的问题,用同样的方法处理。 String( String & s) { str = new char[strlen(s.str)+1]; strcpy(str,s.str); }
-
运算符重载为友元函数
一般情况下,将运算符重载为类的成员函数,是较好的选择。但有时,重载为成员函数不能满足使用要求,重载为普通函数,又不能访问类的私有成员,所以需要将运算符重载为友元。
class Complex { double real,imag; public: Complex( double r, double i):real(r),imag(i){ }; Complex operator+( double r ); }; Complex Complex::operator+( double r ) { //能解释 c+5 return Complex(real + r,imag); }
经过上述重载后:
Complex c ; c = c + 5; //有定义,相当于 c = c.operator +(5); 但是: c = 5 + c; //编译出错 所以,为了使得上述的表达式能成立,需要将 + 重载为普通函数。 Complex operator+ (double r,const Complex & c) { //能解释 5+c return Complex( c.real + r, c.imag); }
但是普通函数又不能访问私有成员,所以,需要将运算符 + 重载为友元:
class Complex { double real,imag; public: Complex( double r, double i):real(r),imag(i){ }; Complex operator+( double r ); friend Complex operator + (double r,const Complex & c);// 重载为友元函数 };
-
编写可变长整型数组类
int main() { //要编写可变长整型数组类,使之能如下使用: CArray a; //开始里的数组是空的 for( int i = 0;i < 5;++i) a.push_back(i);// 需要动态内存分配来存放数组元素 CArray a2,a3; a2 = a; // 重载 = for( int i = 0; i < a.length(); ++i ) cout << a2[i] << " " ; // 重载[] a2 = a3; //a2是空的 for( int i = 0; i < a2.length(); ++i ) //a2.length()返回0 cout << a2[i] << " "; cout << endl; a[3] = 100; CArray a4(a); // 编写复制(拷贝)构造函数 for( int i = 0; i < a4.length(); ++i ) cout << a4[i] << " "; return 0; } 程序输出结果是: 0 1 2 3 4 0 1 2 100 4 要做哪些事情? 要重载“=” 要用动态分配的内存来 存放数组元素,需要一 个指针成员变量 要重载“[ ]” 要自己写复制构造函数
class CArray { int size; // 数组元素的个数 int *ptr; // 指向动态分配的数组 public: CArray(int size = 0); CArray(CArray &a); // 拷贝构造函数 ~CArray(); void push_back(int v); CArray & operator = (const CArray &a); // 用于对象间的赋值 int length() {return size;} int & CArray::operator [] (int i) { // 返回值为int 不行,不支持a[i] = 4这样的操作 return ptr[i]; } }; // 构造函数 CArray :: CArray(int s) : size(s) { if (s == 0) ptr = NULL; else ptr = new int[s]; } // 拷贝构造函数 例如CArray a4(a) CArray :: CArray(CArray &a) { if (!a.ptr) { // 若拷贝的对象 a 为空 ptr = NULL; size = 0; return; } // 若不为空,则分配空间进行复制 ptr = new int[a.size]; memcpy(ptr, a.ptr, sizeof(int) * a.size); size = a.size; } //析构函数 CArray :: ~CArray() { if (ptr) delete []ptr; } //重载 = 例如CArray a2; a2 = a; CArray& CArray :: operator= (const CArray &a) { //赋值号的作用是使 "=" 左边对象里存放的数组,大小和内容都和右边的对象一样 if (ptr == a.ptr) // 防止a = a这样的赋值导致出错 return *this; if (a.ptr == NULL) { // 如果 a 里面的数组是空的 if (ptr) delete []ptr; ptr = NULL; size = 0; return *this; } if (size < a.size) { // 若原有空间够大,就不用分配新的空间 if (ptr) delete []ptr; ptr = new int[a.size]; } //分配新的空间 memcpy(ptr, a.ptr, sizeof(int) * a.size); size = a.size; return *this; } //添加元素 void CArray :: push_back(int val) { if (ptr) { int* tmpPtr = new int[size + 1]; // 重新分配空间 memcpy(tmpPtr, ptr, sizeof(int) * size); // 拷贝原数组内容 delete []ptr; ptr = tmpPtr; } else { // 若数组本来是空的 ptr = new int[1]; } ptr[size++] = val; // 加入新的元素 }
-
流插入运算符和流提取运算符的重载
1.cout 是在 iostream 中定义的,ostream 类的对象
2.“<<” 能用在cout 上是因为,在iostream里对 “<<” 进行了重载
3.考虑,怎么重载才能使得:cout << 5; 和 cout << “this”都能成立?
cout << 5 ; 即 cout.operator<<(5);
cout << “this”; 即 cout.operator<<( “this” );
所以怎么样才能使得cout << 5 << this;成立呢,即对cout.operator << (5)的返回值有要求ostream & ostream::operator<<(int n) { …… //输出n的代码 return * this; } ostream & ostream::operator<<(const char * s ) { …… //输出s的代码 return * this; }
上诉对应的调用形式为cout.operator<<(5).operator<<(“this”);
-
要输出一个对象
• 假定下面程序输出为 5hello, 该补写些什么 class CStudent { public: int nAge; }; int main() { CStudent s ; s.nAge = 5; cout << s <<"hello"; return 0; } //流插入运算符的重载 ostream & operator<<( ostream & o,const CStudent & s) { o << s.nAge ; return o; }
-
重载类型转换运算符
类型强制转换运算符被重载时不能写返回值类型,实际上其返回值类型就是该类型强制转换运算符代表的类型
#include <iostream> using namespace std; class Complex { double real,imag; public: Complex(double r=0,double i=0):real(r),imag(i) { }; operator double () { return real; } //重载强制类型转换运算符 double }; int main() { Complex c(1.2,3.4); cout << (double)c << endl; //输出 1.2 double n = 2 + c; //等价于 double n=2+c.operator double() cout << n; //输出 3.2 }
-
自增运算符++、自减运算符–有前置/后置之分,为了区分所重载的是前置运算符还是后置运算符,C++规定
1.前置运算符作为一元运算符重载
重载为成员函数:T & operator++(); T & operator--();
重载为全局函数:
T1 & operator++(T2); T1 & operator—(T2);
2.后置运算符作为二元运算符重载,多写一个没用的参数:
重载为成员函数:T operator++(int); T operator--(int);
重载为全局函数:
T1 operator++(T2,int ); T1 operator—( T2,int); /*但是在没有后置运算符重载而有前置重载的情况下, 在vs中,obj++ 也调用前置重载,而dev则令 obj ++ 编译出错*/
-
实现:
T & T::operator++() { //前置 ++ n ++; return * this; } // ++s即为: s.operator++(); T T::operator++( int k ) { //后置 ++ T tmp(*this); //记录修改前的对象 n ++; return tmp; //返回修改前的对象 } // s++即为: s.operator++(0); T & operator--(T & d) {//前置-- d.n--; return d; } //--s即为: operator--(s); T operator--(T & d,int) {//后置-- T tmp(d); d.n --; return tmp; } //s--即为: operator--(s, 0);
-
运算符重载的注意事项
1.C++不允许定义新的运算符 ;
2.重载后运算符的含义应该符合日常习惯;
complex_a + complex_b
word_a > word_b
date_b = date_a + n
3.运算符重载不改变运算符的优先级;
4.以下运算符不能被重载:“.”、“ * ”、 “::”、“?:”、sizeof;
5.重载运算符()、[]、->或者赋值运算符=时,运算符重载函数必须声明为类的成员函数 -
第四周作业
/* 补足 MyString 类,使程序输出指定结果 输入 多组数据,每组一行,是两个不带空格的字符串 输出 对每组数据,先输出一行,打印输入中的第一个字符串三次 然后再输出一行,打印输入中的第二个字符串三次 输入样例 abc def 123 456 输出样例 abc,abc,abc def,def,def 123,123,123 456,456,456 */ #include <iostream> #include <string> #include <cstring> 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; } //your code starts here 重载 << friend ostream & operator << (ostream & o,const MyString & s) { o << s.p; return o; } // 复制函数 void Copy(const char * s) { if( p ) //先把原有的数据删除 delete [] p; if( s ) { // 分配空间复制数据 p = new char[strlen(s)+1]; strcpy(p,s); } else { p = NULL; } } // 拷贝构造函数 MyString (const MyString & s) { if( s.p ) { p = new char[strlen(s.p)+1]; strcpy(p,s.p); } else { p = NULL; } } // 重载 = MyString & operator = (const MyString & s) { if( this == & s) { return * this; } if( s.p ) { p = new char[strlen(s.p)+1]; strcpy(p,s.p); } else { p = NULL; } } // 重载 = MyString & operator = (const char * s) { if( p) delete [] p; if( s ) { p = new char[strlen(s)+1]; strcpy(p,s); } else p = NULL; } //your code ends here }; 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; } }
/* 程序填空 输入 多组数据,每组一行,整数 n 输出 对每组数据,输出一行,包括两个整数, n-5 和 n - 8 输入样例 20 30 输出样例 15,12 25,22 */ #include <iostream> using namespace std; class MyInt { int nVal; public: MyInt( int n) { nVal = n ;} //your code starts here // 重载 () operator int() { return nVal;} // 重载 - MyInt & operator - (int i) { nVal -= i; return * this; } //your code ends here }; 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; }
/* 程序填空 输入 多组数据,每组两个整数 输出 对每组数据,输出一行,就是输入的两个整数 输入样例 2 3 4 5 输出样例 2,3 4,5 */ #include <iostream> using namespace std; class Point { private: int x; int y; public: Point() { }; //your code starts here //重载 << friend ostream & operator << ( ostream & o, const Point & p) { cout << p.x << "," << p.y; return o; } //重载 >> friend istream & operator >> (istream & i,Point & p) { i >> p.x >> p.y; return i; } //your code ends here }; int main() { Point p; while(cin >> p) { cout << p << endl; } return 0; }
#include <iostream> using namespace std; /* 用一维数组来存放二维数组 a[i][j]的计算过程从左到右, a[i] 的返回值是个指针, 指向第 i 行的首地址。 a[i][j] 就会是第 i 行第 j 个元素了。 */ class Array2 { //your code starts here private: int * p; int r,c; public: // 构造函数 Array2() { p = NULL ; } Array2( int r_, int c_ ):r(r_),c(c_) { p = new int [ r * c]; } Array2( Array2 & a ):r(a.r),c(a.c) { p = new int [r * c]; memcpy( p, a.p, sizeof(int )*r*c); } // 重载 = Array2 & operator=(const Array2 & a) { if( p ) delete [] p; // 分配空间 r = a.r; c = a.c; p = new int [r * c]; // 拷贝 memcpy( p, a.p, sizeof(int ) * r * c); return * this; } ~Array2() { if( p) delete [] p; } // 重载 [] int * operator [] ( int i ) { return p + i * c; } // 重载 () int & operator() ( int i,int j ) { return p[ i * c + j]; } //your code ends here }; 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; for( i = 0;i < 3; ++i ) { for( j = 0; j < 4; j ++ ) { cout << b[i][j] << ","; } cout << endl; } return 0; }
/* 程序填空,输出指定结果 输入 多组数据,每组数据是两个非负整数 s 和 n。s 最多可能 200 位, n 用 int 能表示 输出 对每组数据,输出 6 行,内容分别是: s+n s+n s+n 2n+1 2n+1 2n+2 样例输入 99999999999999999999999999888888888888888812345678901234567789 12 6 6 样例输出: 99999999999999999999999999888888888888888812345678901234567801 99999999999999999999999999888888888888888812345678901234567801 99999999999999999999999999888888888888888812345678901234567801 25 25 26 12 12 12 13 13 14 大数相加思想: 1.把整数倒序存储,整数的个位存于数组0下标位置, 最高位存于数组长度-1下标位置。之所以倒序存储, 更加符合我们从左到右访问数组的习惯。 2.创建结果数组,结果数组的最大长度是较大整数的位数+1,原因很明显。 3.遍历两个数组,从左到右按照对应下标把元素两两相加,就像小学生计算竖式一样 4.把Result数组的全部元素再次逆序,若首位为0;去掉首位的,就是最终结果 */ #include <iostream> #include <cstring> #include <cstdlib> #include <cstdio> using namespace std; const int MAX = 110; class CHugeInt { //your code starts here private: char buf[220]; // 固定一个220位大小的数组,便于进行对齐 public: // 将存储数据的数组进行逆序操作,符合竖式计算从低位向高位进行的原理 // 因为数组是从 0 开始存储的 0 1 2 3 4 5 6 7 8 9 ... void reverse(char * p) { int len = strlen(p); int i = 0,j = len -1; while(i <= j ) { swap(p[i],p[j]); ++i; --j; } } // 构造函数 char* 型 CHugeInt(char * p) { memset(buf,0,sizeof(buf)); strcpy(buf,p); reverse(buf); } // 构造函数 int 型 CHugeInt(int n) { memset(buf,0,sizeof(buf)); sprintf(buf,"%d",n); // 向字符串中写入数据 reverse(buf); } // 重载3个+运算符,分别对应CHugeInt + int,int + CHugeInt,CHugeInt + CHugeInt CHugeInt operator+(int n) { return * this + CHugeInt(n); } CHugeInt operator +(const CHugeInt & n) const { CHugeInt tmp(0); int carry = 0; for(int i = 0;i < 210 ; ++i) { char c1 = buf[i]; char c2 = n.buf[i]; if( c1 == 0 && c2 == 0 && carry == 0) break; if( c1 == 0) c1 = '0'; if( c2 == 0) c2 = '0'; int k = c1 - '0' + c2 - '0' + carry; if( k >= 10) { // 相加大于10则进位 carry = 1; // 进位位置1 tmp.buf[i] = k - 10 + '0'; } else { carry = 0; tmp.buf[i] = k + '0'; } } return tmp; // 返回对象 } // 当声明在类的外部时,则参数列表为2个参数,所以需要声明为友元,便于访问数据h friend CHugeInt operator +(int n,const CHugeInt & h) { return h+n; } friend ostream & operator <<(ostream & o,const CHugeInt & h) { int len = strlen(h.buf); for(int i = len -1 ; i >= 0; -- i) cout << h.buf[i]; return o; } CHugeInt & operator += (int n) { * this = * this + n; return * this; } /** 关于++ --符号的重载; 为了与内置版本保持一致,前置+±- 应该返回对象的引用,后置+±-应该返回对象原值。 为了区别前置后置,后置运算符接受一个额外的(不被使用的)int类型的形参。 */ CHugeInt & operator ++() { // 前置++ 返回引用且无形参 * this = * this + 1; return * this; } CHugeInt operator ++(int ) { CHugeInt tmp(*this); * this = tmp + 1; return tmp; } //your code ends here }; 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; }