c++复习 ,教材谭浩强《C++程序设计》第三版(以防你们需要这本书的电子版,特意上传到了蓝奏云),尽管这本书有些小错误,但是谭浩强讲的确实很通俗易懂。因为我是自学,虽然已经买了郑莉的书,但是谭浩强的更易懂。下面是我照着抄的代码,作为记录,方便自己复习。
第九章 怎样使用类和对象
构造函数
#include<iostream>
using namespace std;
class box {
int l, w, h;
public:
box() {
l = 10;
w = 10;
h = 10;
}
int volume() {
return l * w * h;
}
};
int main() {
box box1;//一定不能带括号
cout << box1.volume() << endl;
}
总之,如果对象没有带参数,就不要带括号!!!如果带了括号,会被认为声明一个返回值类型为box的函数。
总之,一个类可以不用参数调用的构造函数只能有一个,不然会出现二义性。
析构函数
各类指针
1.函数指针
调用函数的通常形式“函数名(参数表)”的实质就是“函数代码首地址(参数值)”。函数指针就是专门用于存放函数代码首地址的变量,也就是一旦函数指针指向某个函数,它与函数名的作用完全一样。 声明函数指针时,需要说明函数的返回值,参数列表。
语法形式:数据类型 (*函数指针名)(参数列表) int(*m)(int, int); //声明函数指针,记得加括号,不然就变成了声明返回值为int指针的函数
赋值语法形式:函数指针名=函数名 m = &max; //&可省略
调用形式:(*m)(1,2);或者m(1,2);
2.对象指针:就是用于存放对象地址的指针的变量。
声明形式:类名 *对象指针名;
赋值形式:对象指针名=&对象名; time* pt=&t;
访问对象成员:(*对象指针名).对象成员;或对象指针名->对象成员; pt->display(); (*pt).display();
3.指向类成员的指针
1.指向对象非静态数据成员的指针
声明形式:数据类型 *指针名; int* s = &t.second;
赋值形式:指针名=&对象名.数据成员名 (数据是公有才能被访问)
2.指向类静态数据成员的指针
声明形式:数据类型 *指针名;
赋值形式:指针名=&类名::数据成员名(数据是公有才能被访问) int *c = &time::count;
int* c2 = &t.count;/也可以和非静态数据成员一样通过对象调用
3.指向类成员函数的指针
声明形式:数据类型 (类名::指针名)(参数列表);
赋值形式:指针名=&类名::成员函数名 (&可以省去)
void (time::* d)() = &time::display; //括号不能省掉
#include<iostream>
using namespace std;
class time {
public:
time(int h, int m, int s) :hour(h), minute(m), second(s) {
count++;
}
void display() {
cout << hour << ":" << minute << ":" << second << endl;
}
int second;
static int count;
private:
int hour, minute;
};
int time::count = 0;
int max(int a,int b) {
return a > b ? a : b;
}
int main() {
//1.调用函数的通常形式“函数名(参数表)”的实质就是“函数代码首地址(参数值)”
//函数指针就是专门用于存放函数代码首地址的变量,也就是一旦函数指针指向某个函数,它与函数名的作用完全一样
//声明函数指针时,需要说明函数的返回值,参数列表。
//语法形式:数据类型 (*函数指针名)(参数列表) 赋值语法形式:函数指针名=函数名
int(*m)(int, int); //声明函数指针,记得加括号,不然就变成了声明返回值为int指针的函数
m = &max; //&可省略
cout << m(1, 2) << endl;
//2.对象指针:就是用于存放对象地址的指针的变量。
//声明形式:类名 *对象指针名; 赋值形式:对象指针名=&对象名; 访问对象成员:(*对象指针名).对象成员;或对象指针名->对象成员;
time t(1,2,3);
time* pt=&t;
pt->display();
(*pt).display();
//3.1指向对象非静态数据成员的指针
//声明形式:数据类型 *指针名;
//赋值形式:指针名=&对象名.数据成员名 (数据是公有才能被访问)
int* s = &t.second;
cout << *s << endl;
//3.2指向对象静态数据成员的指针
//声明形式:数据类型 *指针名;
//赋值形式:指针名=&类名::数据成员名(数据是公有才能被访问)
int *c = &time::count;
int* c2 = &t.count;
cout << *c<<endl; //也可以和非静态数据成员一样通过对象调用
//3.3指向对象成员函数的指针
// 声明形式:数据类型 (类名::指针名)(参数列表);
//赋值形式:指针名=&类名::成员函数名 (&可以省去)
void (time::* d)() = &time::display;//括号不能省掉
}
共用数据的保护
1.常对象
定义形式:类名 const 对象名[(实参表)];或者const 类名 对象名[(实参表)]; 定义常对象,必须同时对其初始化,之后不能再改变。常对象只能调用常成员函数
2.常数据成员
只能通过构造函数的初始化列表对常数据成员进行初始化,任何其他函数都不能对常数据成员赋值。
3.常成员函数
声明形式:类型 函数名(参数表) const。在声明和定义常成员函数时一定要加const,但调用时可以不加。常成员函数只能引用本类数据成员,而不能修改。
友元
#include<iostream>
using namespace std;
//提前引用声明
class date;
class time {
public:
time(int, int, int);
void display(date &d);
private:
int hour, minute, second;
};
time::time(int a, int b, int c) :hour(a), minute(b), second(c) {
}
class date {
public:
date(int y,int m,int d):year(y),month(m),day(d) {
}
//需要声明类time的display为友元函数,而不是仅仅声明display为友元函数
friend void time::display(date& d);
private:
int year, month, day;
};
void time::display(date& d) {
cout << d.year<<"/"<<d.month<<"/"<<d.day<<"\t";
cout << hour << ":" << minute << ":" << second << endl;
}
int main() {
time t(13, 41, 56);
date d(2020, 11, 28);
t.display(d);
}
当声明成员函数为友元函数时候,要记得加类名修饰。
类模板
#include<iostream>
using namespace std;
template <typename T>
class compare {
private:
T a, b;
public:
//构造函数
compare(T a, T b) :a(a), b(b) {
}
T max();
T min();
};
//如果将成员函数写在类外,需要附带template
//而且类名后面还得加类型<T>
template<typename T>
T compare<T>::min() {
return a < b ? a : b;
}
template<typename T>
T compare<T>::max() {
return a > b ? a : b;
}
int main() {
//需要在类模板名后面用尖括号指定实际的类型名
compare <int>a(4, 5);
cout << a.min() << endl;
compare<double> m(2.2, 8343.0);
cout << m.min() << endl;
compare<bool>c(0, 10);
cout << c.max();
return 0;
}
总之,就是一定要记得声明对象时用尖括号说明实际类型,还有函数体放在类外的时候要加template<typename T>; 而且同声明对象一样,也要在类名后面加尖括号说明虚拟类型。
第十章运算符重载
运算符重载为成员函数
#include<iostream>
using namespace std;
class complex {
double real, image;
public:
complex() {
real = 0;
image = 0;
}
complex(double real,double image): real(real),image(image){
}
complex operator+ (complex& c2) {
return complex( real+c2.real,image+c2.image); //创建临时无名对象
}
complex operator-(complex&);
void display();
};
complex complex::operator -(complex& c2) {
return complex(real - c2.real, image - c2.image);
}
void complex::display() {
cout << "(" << real << "," << image << "i)" << endl;
}
int main() {
complex c1(5, 6),c2(1,1),c3;
cout << "c1="; c1.display();
cout << "c2="; c2.display();
c3 = c1 + c2;
cout << "c3=c1+c2="; c3.display();
}
第十一章派生类
其中很重要的就是三种继承方式:public、private、protected。
派生类的构造函数和析构函数
派生类构造函数的一般形式:派生类构造函数名(总参数表):基类构造函数名(参数表){派生类新增数据成员的初始化语句 }
在执行派生类的构造函数时,会调用基类的构造函数。
#include<string>
#include<iostream>
using namespace std;
class student {
public:
//定义基类构造函数
student(int num,string name):num(num),name(name) {
}
protected:
int num;
string name;
};
//声明公有继承
class student1 :public student {
int age;
char gender;
public:
//定义派生类的构造函数
student1(int num, string name, int age, char gender) :student(num,name),age(age),gender(gender){
}
void display() {
cout << "num:" << num << endl;
cout << "name:" << name << endl;
cout << "age:" << age << endl;
cout << "gender:" << gender << endl;
}
};
int main() {
student1 s1(101,"liu",22,'F');
s1.display();
return 0;
}
在定义派生类的构造函数时,同时也要给基类的构造函数初始化,注意,这里只是调用基类的构造函数,而不是定义基类的构造函数。
如果在类外定义派生类的构造函数,在类中对构造函数进行原型声明时,不用写“基类构造函数名(参数表)”,只有在定义函数的时候才将它列出。
class student1 :public student {
int age;
char gender;
public:
//声明派生类的构造函数
student1(int num, string name, int age, char gender);
void display() {
cout << "num:" << num << endl;
cout << "name:" << name << endl;
cout << "age:" << age << endl;
cout << "gender:" << gender << endl;
}
};
//在类外定义构造函数
student1::student1(int num, string name, int age, char gender) :student(num, name), age(age), gender(gender) {
}
多层级派生时的构造函数
一个类不仅可以派生出一个派生类,派生类还可以继续派生。在写构造函数数时,派生类只需要初始化直接基类的构造函数,无须列出每一级间接基类。
#include<string>
#include<iostream>
using namespace std;
//声明间接基类
class student {
public:
//定义基类构造函数
student(int num,string name):num(num),name(name) {
}
protected:
int num;
string name;
};
//声明直接基类
class student1 :public student {
int age;
public:
//声明派生类的构造函数,只需要写直接基类的参数表即可。
student1(int num, string name, int age) :student(num, name), age(age) {
};
void display1() {
cout << "num:" << num << endl;
cout << "name:" << name << endl;
cout << "age:" << age << endl;
}
};
//声明派生类
class student2 :public student1 {
char gender;
public:
student2(int num,string name,int age,char gender):student1(num,name,age),gender(gender) {
}
void display2() {
display1();
cout << "gender:" << gender << endl;
}
};
int main() {
student2 s2(101,"liu",22,'F');
s2.display2();
return 0;
}
派生类的析构函数
派生类是不能继承基类的析构函数的,也需要通派生类的析构函数去调用基类的析构函数。析构函数的调用顺序与构造函数正好相反:先执行派生类自己的析构函数,对派生类新增加的成员进行清理,然后调用子对象的析构函数,对子对象进行清理,最后调用基类的析构函数,对基类进行清理。
多重继承引起的二义性问题
int main{
C c1;
c1.A::a=3; //需要用指明c1的调用的是哪一个直接基类的成员
c1.A::display(); //需要用指明c1的调用的是哪一个直接基类的成员
}
虚基类
注意:虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。 声明虚基类的方式:
class 派生类名:virtual 继承方式 基类名;
在最后的派生类中,不仅要对其直接基类进行初始化,还要虚基类进行初始化。C++编译系统只执行对最后的派生类对虚基函数的调用,而忽略掉虚基类的其他派生类(如类类C、类B),这就保证了虚基类的数据成员不会被多次初始化。
#include<string>
#include<iostream>
using namespace std;
//声明公共基类
class person {
protected:
string name;
char sex;
int age;
public:
person(string name,char sex,int age):name(name),sex(sex),age(age){}
};
//person类的直接派生类,声明继承虚基类
class teacher :virtual public person {
public:
//构造函数
teacher(string name, char sex, int age,string title) :person(name, sex, age), title(title) {
}
protected:
string title;
};
//person类的直接派生类,用virtual声明person为虚基类
class student :virtual public person {
public:
student(string name, char sex, int age, float score) :person(name, sex, age), score(score) {
}
protected:
float score;
};
//声明多重继承graduate类
class graduate :public student, public teacher {
//要给直接基类初始化,还要给间接基类初始化
public:
graduate(string name, char sex, int age,string title,float score,float wage):person(name,sex,age),teacher(name,sex,age,title),student(name,sex,age,score),wage(wage) {
}
void display() {
cout << "name:" << name << endl;
cout << "sex:" << sex << endl;
cout << "age:" << age << endl;
cout << "title:" << title << endl;
cout << "score:" << score << endl;
cout << "wage:" << wage << endl;
}
protected:
float wage;
};
int main() {
graduate g1("wang", 'f', 22, "assistant", 90, 10000);
g1.display();
}
累死我了,在给graduate写初始化列表时,真的太长一串了。虚基类牛逼,要给所有的基类初始化。
基类与派生类的转换
注意:只能用子类的对象给其基类对象进行赋值,而不能用基类的对象对其子类对象进行赋值。
第十二章 多态性与虚函数