关于 C++ 面向对象的笔记总结
//类的定义
class <类名>
{
private:
...;
protected:
...;
public:
...;
};
注意:成员变量不能在声明时初始化(但静态常成员函数可以);不要与JAVA的混淆
//成员函数的定义
//先搞清楚声明与定义的区别,声明与初始化的区别
<返回类型> <类名>::<成员函数名>([参数表])
{
...;
}
//带默认值的成员函数
只有在声明时才指定默认值
默认参数从右到左逐渐定义
void f(int a1, int a2 =0, int a3 = 9);
//类的内联成员函数:
隐式implicit: 在类中同时声明与定义
显式explicit: inline
类中声明: inline <返回类型> <函数名>([参数表]);
类外定义: inline <返回类型> <类名>::<函数名>([参数表]) //inline可省
{
...
}
//普通的内联函数:
声明:inline <返回类型> <函数名>([参数表]);
定义:inline <返回类型> <类名>::<函数名>([参数表]) //inline可省
{
...
}
或者声明定义写一起
inline <返回类型> 函数名([参数表]) {...}
//成员函数重载
相同的函数名,不同的参数列表(考虑:参数类型及参数个数)
//用指针或引用访问成员函数(或变量)
<类名> obj;
<类名> *p = &obj; //可以不在声明时初始化
<类名> &ref = obj; //必须在声明时初始化(赋值)
p->xxx
ref.xxx
//常成员函数:
只“读”不可“写”不能在函数中改变参数的值
类中声明: <返回类型> <函数名>([参数表]) const;
类外定义: <返回类型> <类名>::<函数名>([参数表]) const //const 不可省
{
...
}
//构造函数与析构函数
构造函数:对象生成时调用(如声明, new 等)
当没有自己定义构造函数时,编译器提供一个缺省的无参构造函数
这个函数相当于 <类名>(){}
若自己定义了一个构造函数(不管有参无参),则编译器不再提供缺省的无参构造函数
基本数据类型(整型等)的成员变量,不会自动初始化(一个随机的值) //不要与JAVA混淆
析构函数:对象生命周期结束时调用
显式调用构造函数可以创建一个无名对象,多用于传参
void fun(Point pt);
fun(Point(x,y));
可以重载多个不同参数的构造函数
可以带默认参数
用带默认参数的构造函数重载时,注意避免二义性错误如 Point() 与 Point(int x = 0, int y = 0),调用Point()时就不知道调用的是哪一个
注意:以下两个语句的区别
<类名> obj; //对象声明
<类名> obj(); //函数声明
//不同作用域范围对象的构造与析构顺序
局部对象: 按声明的先后顺序(可能出现多次生灭)
局部静态对象:按声明的先后顺序(只析构一次)
全局对象: 单文件(按声明先后);多文件(未知)
全局静态对象:单文件(按声明先后);多文件(未知);只析构一次
对象成员: 按声明先后(与冒号语法中初始化表达式的顺序无关)
对象生命周期结束时调用析构函数,顺序与构造函数的顺序相反
//堆对象
可以在用 new 分配空间时调用带参数的构造函数进行初始化 Point* p = new Point(1,2);
delete 时析构
delete p;
delete[] psArray;
先析构对象,再释放空间
//拷贝构造函数
注意“生成临时对象”的情况,很多情况下生成临时对象时会调用拷贝构造函数
1. 在函数参数表中用对象时(非引用)会产生一个临时对象,并将调用拷贝构造函数
void f(OBJ obj); OBJ obj; f(obj);
2. 在函数返回对象时,也会产生一个临时对象,并将调用拷贝构造函数
OBJ f() {OBJ obj; return obj;}
3. 在函数返回对象引用时,不产生临时对象,不调用拷贝构造函数
OBJ& f(){OBJ obj; return obj;}
//注意一般不能返回局部变量的引用,这里只作演示,说明返回引用时不产生临时对象
4. 在函数返回临时无名对象时,不再产生临时对象,不调用拷贝构造函数
OBJ f(){return OBJ();}
区别:浅拷贝 (只拷贝栈内存,不分配堆内存) 与深拷贝(一般由自己定义)
编译器提供一个缺省的拷贝构造函数,它以浅拷贝的方式实现,若有动态成员(指针),赋值后在析构时会出现问题(同一块内存被释放了两次)
class A
{
private:
int a;
public:
A(){a = 0; cout << "construct with no param\n";}
A(int x){a = x; cout << "construct with " << x << "\n";}
A(const A& obj)
{
this->a = obj.a;
cout << "copy construct with " << a << endl;
}
}
A f() {return A();}
A a1; // construct with no param
A a2 = a1; // copy construct with 0
A a3; // construct with no param
a3 = a1; //没有输出
A a4; // construct with no param
a4 = f(); // 在f 中输出construct with no param;= 没有输出
A a5 = f(); // 在f 中输出construct with no param;= 没有输出
声明时
默认的赋值操作为浅拷贝
自己定义一个拷贝构造函数
Point::Point(const Point& point);
//无名对象的使用
1. 作为实参传递给函数
void fun(OBJ obj); fun(OBJ());
2. 用来构造新对象
Point p = Point(1,2); <=> Point p(1,2);只调用一次构造函数,不调用拷贝构造函数
3. 初始化一个引用的声明 Point &ref = Point(3,4);
无名对象将在生产的某个时刻调用构造函数
//构造函数用于类型转换
class LeiXingZhuanHuan
{
public:
LeiXingZhuanHuan(char * s) {
cout << "construct" << endl;
}
LeiXingZhuanHuan(const LeiXingZhuanHuan& x) {
cout << "copy construct" << endl;
}
};
void f3(const LeiXingZhuanHuan& x){}
void f4(LeiXingZhuanHuan x){}
LeiXingZhuanHuan lx1 = LeiXingZhuanHuan("001"); //先调用构造,再调用拷贝构造
LeiXingZhuanHuan lx2 = "001";//直接调用构造,这里隐式调用了构造函数,进行类型转换;隐式调用构造函数用于类于转换时1.只会尝试含有一个参数的构造函数;2.不能存在二义性
LeiXingZhuanHuan lx3("001"); //调用构造
LeiXingZhuanHuan& lxref1 = lx1; //不调用构造函数
f3(LeiXingZhuanHuan("001")); //调用构造函数
f4("004"); //隐式调用了构造函数,相当于实现了类型转换
//作用域
理解函数原型作用域,块作用域,类作用域,文件作用域
//静态成员
通过 <类名>::XXX 或 <对象>.XXX访问 (当然得符合访问权限public)
静态数据成员的初始化:
在类中声明: static int n;
在类外初始化: int A::n = 100; 可以为公有或私有,但必须在全局范围内初始化(即不能在某个函数中),可以在普通成员函数中访问静态成员(读或写,但一般不在构造函数中初始化,因为它是各个类共享的)
class CHasStaticMember
{
static const int c0 = 0; //静态常量成员可以在类中声明时初始化,其他都不行
static const int c1
static int x;
};
//在类外初始化时不加static
const int CHasStaticMember:: c1 = 1;
int CHasStaticMember::x = 0;
静态成员函数只能访问静态成员变量
//友元
1.友元函数:
友元函数是可以直接访问类的私有成员的非成员函数。它是定义在类外的普通函数,它不属于任何类,但需要在类的定义中加以声明,这样就可以访问该类的私有成员了
类中声明:friend ... ,不管声明在哪个访问权限下都可以(public,protected,private)
类外定义:同普通函数(其实就是一普通函数,只是多个一项权利)
class B
{
public:
friend void f(const B& obj);
B():x(0){}
B(int val) : x(val){}
protected:
private:
int x;
};
void f(const B& obj)
{
cout << obj.x << endl;
}
2.友元成员函数(让类B的某个成员函数可以访问类A的私有成员)
class A;
//这个B必须定义在A的前面,因为A中有用的B::f()的原型,但前面要声明一下A
class B
{
public:
void f(const A& obj);
};
class A
{
private:
int a;
public:
friend void B::f(const A& obj);
};
void B::f(const A& obj)
{
cout << obj.a << endl;
}
3.友元类
让类B成为A的友元类
class B;
class A
{
private:
int a;
friend class B;
};
class B
{
public:
void f(const A& obj);
};
void B::f(const A& obj)
{
cout << obj.a << endl;
}
//操作符重载
1.重载为友元函数:
class A;
A operator + (const A& obj1, const A& obj2);
class A
{
public:
A(){a = 0;}
A(int x){a = x;}
void Display()
{
cout << a << endl;
}
friend A operator + (const A& obj1, const A& obj2);
private:
int a;
};
A operator + (const A& obj1, const A& obj2)
{
return A(obj1.a + obj2.a);
}
2.重载为成员函数:
class A
{
public:
A(){a = 0;}
A(int x){a = x;}
void Display()
{
cout << a << endl;
}
A operator + (const A& obj)
private:
int a;
};
A A::operator + (const A& obj)
{
return A(obj.a + a);
}
3.特殊运行符的重载
//前置
A& operator++ ()
{
++a;
return *this;
}
//后置也可以写成 A operator++(int) ,这样 obj1++ = obj2 不会报错,但已经引用不到这个临时变量了
const A operator++(int) // obj++ = obj2 会报错,因为常量不能做左值
{
A t(*this);
a++;
return t;
}
写成友元时
类中:
friend A& operator++ (A& obj);
friend const A operator++ (A& obj, int);
类外:
A& operator++ (A& obj)
{
obj.a++;
return obj;
}
const A operator++(A& obj,int)
{
A t(obj);
obj.a++;
return t;
}
输出输入
ostream& operator << (ostream& o, 类名对象名)
{
o << 对象.成员;
return o;
}
istream& operator >> (ostream& i, 类名& 对象名)
{
i >> 对象.成员;
return i;
}
其他操作符还有 +=,-=,*=,/=,[], , , =, ->,()等
= :联系一下“深拷贝”与“浅拷贝”
[]: 返回类型& operator [] (int i); 当左值时要返回引用
->,[],(),= 只能重载为成员运算符
类型转换运算符
class Length
{
int meter;
public:
Length(int m) {meter = m;}
operator float()
{
return (1.0 * meter / 1000);
}
};
//"组合",一个类中的某个成员变量为某个类的对象,把该对象叫做 "子对象"
子对象的有参构造要使用冒号语法 <对象名>(参数),默认调用无参构造
有多个子对象时,按声明的先后顺序构造,与冒号语句的顺序无关
//继承
派生类的声明 class <派生类名> : <继承方式> <基类1>[, <继承方式> <基类2>,...]
继承方式: public protected private
public public protected 隔离
protected protected protected 隔离
private private protected 隔离
隔离的对象,可以通过基类中继承下来的可见的成员函数访问
继承分为单继承与多继承
在继承与派生过程中
1. 吸收基类成员(不包括成员函数和析构函数)
2. 改造
a. 继承方式
b. 同名覆盖(注意:函数参数不同时为重载)
3. 添加新成员
//派生类构造与析构
派生类构造函数的调用顺序
1.基类 2.子对象(对象成员) 3.派生类
析构函数的调用顺序相反
如果子类的构造函数不显式调用基类的构造函数的话,会自动调用基类的无参构造函数
如果子类的构造函数显式调用了基类的一个有参构造函数,则不再调用基类的无参构造函数
显式调用基类的一个有参构造函数只能使用冒号语法,用类名
成员对象的构造也要使用冒号语法,但两者有区别,用变量名
//冒号语法
组合与继承的冒号语法的区别:
给合:使用对象名(变量名)
继承:使用类名
另外,常量和引用的初始化要用冒号语法,且只能使用冒号语法
也就是说使用冒号语法的时机有:
调用子类的有参构造函数,子对象,常量,引用的初始化
//一个单继承的例子
class Point
{
public:
Point(int x = 0, int y = 0)
{
this->x = x;
this->y = y;
}
int GetX()
{
return x;
}
int GetY()
{
return y;
}
void SetX(int x = 0)
{
this->x = x;
}
void SetY(int y = 0)
{
this->y = y;
}
void Display()
{
cout << "(" << x << ", " << y << ")";
}
protected:
private:
int x, y;
};
class Circle : public Point
{
public:
//派生类的构造函数:注意调用基类构造函数与构造子对象的不同之处
Circle(int x = 0, int y = 0, int r = 0) : Point(x,y), r(r){}
int GetR()
{
return r;
}
void SetR(int r)
{
this->r = r;
}
//覆盖基类的Display();
void Display()
{
//调用基类的Display();
Point::Display(); cout << " r = " << r;
}
void Test()
{
//通过类型的函数访问从基类中继承下来的但被隔离的对象
cout << GetX() << ", " << GetY() << ", " << r << endl;
}
private:
int r;
};
//一个多继承的例子
class B1
{
public:
int b, b1;
B1(int b = 0, int b1 = 1) : b(b), b1(b1){}
void f()
{
cout << "f() in B1" << endl;
}
void f1()
{
cout << "f1() in B1" << endl;
}
};
class B2
{
public:
int b, b2;
B2(int b = 0, int b2 = 2) : b(b), b2(b2){}
void f()
{
cout << "f() in B2" << endl;
}
void f2()
{
cout << "f2() in B2" << endl;
}
};
class D : public B1, public B2
{
public:
int d;
Point p;
//从子类中继承下来的成员不能直接使用冒号语法,而是使用基类类名
D(int b = 0, int b1 = 1, int b2 = 2, int d = 3) : B1(b, b1), B2(b * b, b2), d(d), p(1,2)
{
}
/*也可以这样写,但一般用前者
D(int b = 0, int b1 = 1, int b2 = 2, int d = 3) : B1(b, b1), B2(b * b, b2), d(d)
{
p = Point(1,2);
}
*/
void f()
{
cout << "f() in D" << endl;
//调用子类中的方法
B1::f();
B2::f();
}
void Test()
{
cout << B1::b << endl;
cout << B2::b << endl;
cout << B1::b1 << endl;
cout << B2::b2 << endl;
cout << d << endl;
}
};
D d(999,1,2,3);
d.Test();
d.B1::f(); //访问基类的成员函数
d.B1::f1();
d.B2::f();
d.B2::f2();
//派生类成员的标识与访问
基类名::成员名
基类名::成员函数([参数列表])
同名覆盖时,将覆盖所有基类的同名成员
若没有覆盖,多基类的同名成员也要通过::访问才能区分(否则会有二义性)
//虚基类:
B0
B1 B2
D1
使得继承时只保存一份拷贝
"D1 通过 B1 继承自 B0" 和 "D1 通过 B2 继承自 B0" 的东西只保存一份拷贝
如果派生类中重新义同名函数(非重载),同样可以同名覆盖
class B0{};
class B1 : virtual public B0 {};
class B1 : virtual public B0 {};
class D1 : public B1, public B2 {};
实例:
class B0
{
public:
B0(int x0 = 0) : v0(x0) { cout << "construct B0 with " << x0 << endl; }
~B0(){cout << "~B0\n";}
int v0;
};
class B1 : public B0
{
public:
B1(int x0 = 0, int x1 = 1) : B0(x0),v1(x1) { cout << "construct B1 with " << x0 <<", " << x1 << endl; }
~B1(){cout << "~B1\n";}
int v1;
};
class B2 : public B0
{
public:
B2(int x0 = 0, int x2 = 2) : B0(x0),v2(x2) { cout << "construct B2 with " << x0 <<", " << x2 << endl; }
~B2(){cout << "~B2\n";}
int v2;
};
class D : public B1, public B2
{
public:
D(int x0 = 0, int x1 = 1, int x2 = 2) : B1(x0, x1), B2(x0, x2) { cout << "construct D with " << x0 <<", " << x1 <<", " << x2 << endl; }
~D(){cout << "~D\n";}
};
int main()
{
D d;
}
输出结果为:
construct B0 with 0
construct B1 with 0, 1
construct B0 with 0
construct B2 with 0, 2
construct D with 0, 1, 2
~D
~B2
~B0
~B1
~B0
当
class B0{};
class B1 : virtual public B0 {};
class B1 : virtual public B0 {};
class D1 : public B1, public B2 {};
时
输出结果为:
construct B0 with 0
construct B1 with 0, 1
construct B2 with 0, 2
construct D with 0, 1, 2
~D
~B2
~B1
~B0
//虚函数: 用于多态
声明: 类中: virtual 返回类型函数名([参数表]);
定义: 类外: 返回类型类名::函数名([参数表]){...}
注意: 只有在声明时才加 virtual
当基类用virtual声明成员函数为虚函数时,
其派生类的同名函数(重载函数除外)自动成为虚函数(前面可加virtual也可不加)
若在派生类中不重新定义,则直接简单继承基类的虚函数
若在派生类中重新定义, 则通过基类的指针访问可以表现为多态
基类指针只能访问基类的函数或虚函数(其实也是在基类中声明的函数,只是可以表现多态性)
基类的指针,引用常用作函数形参实现多态
Void fun(B0* p);
Void fun(B0& p);
不用virtual 时,只调用基类的函数
工作原理:每个对象多了一个指针空间,指向类中的虚函数表
//虚析构函数:
声明:virtual ~类名();
定义:...
其派生类的析构函数自动变为虚析构函数
同样用基类的指针处理时,可以从子类开始析构,而不是调用基类的析构
最好将基类的析构函数声明为虚析构函数
//几个不同的"虚"
虚基类:使多个来自同一虚基类的相同的东西只拷贝一份,其他同一般的继承,可以进行同名覆盖(覆盖之后若要调用父类的函数父类::函数名)
虚函数:既不覆盖,也不是重载,而是一个函数在不同类中表现出多态;一般通过父类的指针来表现
纯虚函数:基类不需要此功能,只为派生类服务
声明: virtual 返回类型函数名(参数表) = 0;
抽象类:包含有纯虚函数的类或显式的用abstract标记的类
class 类名
{
public:
virtual 返回类型函数名(参数表) = 0;
};
class 类名 abstract { ... };
若抽象类的派生类没有给出所有纯虚函数的实现,则该派生类仍为抽象类
一个虚基类和虚函数同时使用的例子:
B0
B1 B2
B3
B0* p;
若把 p 指向四个不同类的对象时,对B0,B1,B2,B3都能表现出多态,
则必须同时使用虚基类和虚函数
否则无法通过 p 调用 B3 的相应函数(编译无法通过)
//模板
函数模板
template <class T> //可以有多个
返回类型函数名(参数表)
{
...
}
类模板
template <class T> //可以有多个
class <类名>
{
...
};
声明对象 类名<类型> 对象名(参数);
类模板的对象引用,可作函数参数
类模板可以作基类
//异常处理
try
{
throw
}
catch(...)
{
}
//几个"常"
常引用 const int& x; //多用于函数形参
常量 const int PI = 3.14; // <=> int const PI = 3.14;
常函数 void print(int x) const; //只"读"不可"写" ,在函数中不能对x值进行改变
常数据成员
静态:
在类外初始化
class A{static const int x;}; const int A::x = 100; //注意,这里不能加static ,非常量的静态成员初始化时也一样
在类内声明时初始化 class A { static const int x = 100; } // 只有静态常量数据成员才可以在类中声明时初始化
非静态: 在构造时用冒号语法,使用初始化列表初始化
常成员函数
Print(x) const; //只“读”不“写”
//几个不同指针的区别
指针 | 指向的位置 | 所指向的位置是否可变 | 通过该指针是否可以改变所指向位置的内容 |
int* | &int | 是 | 是 |
int* const | &int | 否 | 是 |
const int* | &(const int) | 是 | 否 |
const int* const | &(const int) | 否 | 否 |