目录
类的复制构造函数在以下情况被调用:
const
类型声明和实现复制构造函数的一般方法:
class Point {
public:
Point(Point &p);
private:
int x, y;
};
Point::Point(const Point &p){
x = p.x;
y = p.y;
}
创建组合类的对象时的构造函数调用顺序:
必须在初始化列表中初始化的数据成员:
组合类构造函数定义的一般形式:
类名 :: 构造函数名(形参表) : 内嵌对象1(形参表), ... {
/*构造函数体*/
}
形参表中的形参可以是此类对象的引用(将调用复制构造函数)。
其中内嵌对象1(形参表), 内嵌对象2(形参表), ...
称为初始化列表,其作用是对内嵌对象进行初始化。
class Inner {
public:
Inner(Iparam1, Iparam2, ...);
};
...
class Outer {
public:
Outer(Oparam1, Oparam2, ...);
private:
Inner1 i1;
Inner2 i2;
...
};
Outer :: Outer(Oparam1, Oparam2, ...) : i1(Iparam1, Iparam2, ...), i2(...), ...{
//构造函数主体
}
const
与static
const
关键字const
可以用作区分重载const
作为重载的区分时,普通对象将默认调用普通成员函数class A {
public:
A(int i);
private:
const int a;
static int b;
};
//在初始化列表中初始化常量
A::A(int i):a(i){
//构造函数的其他内容
}
//在类外初始化静态成员
int A::a = 1;//必须要加数据类型关键字
//下面的做法是错的,声明和实现不能重复设置默认值
void fun(int a = 1, int b = 2);
void fun(int a = 1, int b = 2){ }
MyEnum.
或MyEnum::
是错的myEnum = MyEnum(number);
MyEnum myEnum; myEnum = MyEnum(1);
则e的值为1,无论枚举中是否有1这个值throw
可以抛出普通数据类型,也可以抛出类的对象catch
的参数可以是普通数据类型,也可以是类对象catch
按顺序检查是否可以捕获所抛出的异常catch
捕获,则后面的catch
不会执行catch(...)
,则该catch子句可以捕获所有类型的异常catch
块必须放在最后catch
后的异常类型可以不声明形参,但这样无法访问所捕获到的异常对象。
使用不带操作数的throw
可以将当前捕获的异常再次抛出,但是只能在catch
块或catch
块中调用的函数中使用。
若异常抛出点本身不在任何try-catch
块内,则结束当前函数的执行,回到当前函数的调用点,把调用点作为异常的抛出点,然后重复这一过程。
/*throw表达式语法*/
throw 表达式 ;
/*try-catch块语法*/
try {
//可能发生异常的内容
} catch (异常类型1 形参名) {
//捕获到异常后的处理方法
} catch (异常类型2 形参名) {
//将当前捕获到的异常再次抛出,将抛出源异常对象,而不是其副本
throw;
} catch (...){
//使用...捕获所有类型的异常
}
throw ()
的形式,则此函数不抛出任何异常如果函数抛出了异常接口声明中所没有的异常,unexpected
函数会被调用,该函数默认会调用terminate
函数中止程序。用户可以自定义unexpected
函数的行为。
/*在函数声明中说明函数可以抛出哪些异常*/
返回类型 函数名(参数表) throw (异常类型1, 异常类型2, ...);
/*不抛出任何异常的函数*/
返回类型 函数名(参数表) throw ();
发生异常时,从进入try
块(捕获到异常的catch
所对应的那个)直到异常抛出前,这期间栈上构造的并且没被析构的所有对象都会被自动析构,这一过程被称为栈的解旋。
string
类型的异常的注意事项当抛出的异常为string
类型时,要注意不能直接抛出匿名字符串,而要先声明一个string
类型的变量或常量,然后再将该变量或常量抛出。
void fun() throw (string) {
...
const string str = "msg";
throw msg;
//throw "msg";//这样写的话捕获不到异常
}
class A {
...
friend class B;//声明友元类B
friend void fun();//声明友元函数fun()
...
}
static
和const
成员//使用基类名限定
obj.Base1::var;
obj.Base2::fun();
//使用作用域标识符限定
class Derived:public Base1, public Base2{
...
using Base1::var;
using Base2::fun;//不加括号
...
}
公有继承 public
当类的继承方式为公有继承时,基类的public
成员和protected
成员的访问属性在派生类中不变,而基类的private
成员不可直接访问。
保护继承 protected
当类的继承方式为保护继承时,基类的public
成员和protected
成员的访问属性在派生类中变为protected
,而基类的private
成员不可直接访问。
私有继承 private
当类的继承方式为私有继承时,基类的public
成员和protected
成员的访问属性在派生类中变为private
,而基类的private
成员不可直接访问。默认的继承方式为private
。
若派生的的多个直接基类还有共同的基类,则直接基类中从共同基类继承来的成员有相同的名称。在派生类对象中这些同名数据成员在内存中同时拥有多个副本,同名函数会有多个映射。
可以使用作用域标识符来区分它们,也可以将共同基类设为虚基类,这时从不同路径继承来的同名数据成员在内存中就只有一个副本,同名函数也只有一个映射。
//class 派生类名:virtual 继承方式 基类名
class Base0{};
class Base1:virtual public Base0{};
class Base2:virtual public Base0{};
class Drived:public Base1, public Base2{};//Base0不是Drived的直接基类,不加virtual
派生类构造函数的语法形式:
派生类名::构造函数名(参数表):基类名(参数表), ..., 派生类初始化列表{
//派生类函数体
}
如果虚基类有含参构造函数,并且没有声明无参构造函数,则在整个继承过程中,直接或者间接继承虚基类的所有派生类,都必须在构造函数的初始化列表中显式对虚基类初始化。
虚基类的构造函数不会被多次调用,只有距离虚基类最远的派生类的构造函数才会调用虚基类的构造函数,而其他派生类对虚基类的构造函数的调用会被自动忽略。
class Base0{
public:
Base0(param);//虚基类含参构造函数
};
class Base1:virtual public Base0{
public:
Base1(param):Base0(param);//初始化列表传参
};
class Base2:virtual public Base0{
public:
Base2(param):Base0(param);//初始化列表传参
};
class Drived:public Base1, public Base2{
public:
Drived(param):Base0(param);//初始化列表传参
};
派生类对象的构造顺序:
如果为派生类编写复制构造函数,一般要为其基类的复制构造函数传递参数。
应该将派生类对象作为其基类复制构造函数的参数。
Derived::Derived(const Derived ¶m):Base(param), ...{
//派生类复制构造函数体
}
在定义类之前使用该类,需要使用前向引用声明。
在提供类的完整定义之前,不能定义该类的对象,也不能在内联成员函数中使用该类的对象。
class B;//前向引用声明
class A {
...
B b;//错误!类A不完整,不能定义它的对象
B &rb;//正确
B *pb;//正确
};
class B {
...
};
cin
、gets()
和getline()
使用cin
读取数据时,遇到空格会停止读入。
使用gets(char*)
和getline(cin, string, char)
读入一整行数据。
如果在gets()
或getline()
函数前使用了cin
,注意要清除cin
留下的换行符,参见下面的示例。
getline()
默认使用换行\n
作为读取结束的标志,也可以自定义结束标志。
在getline()
函数的第三个参数处设置结束标志,传入的字符将会最为结束的标志(\n
仍然有效)。
char ch[100];
string str;
gets(ch);
getline(cin, str);
getline(cin, str, ',');//将半角逗号`,`设为读取结束标志
/*
* 如果gets()或者getline()函数的前一行使用cin读取数据,
* 那么应该在gets()或者getline()函数之前使用getchar(),
* 否则gets()或者getline()会把cin结束的换行符读入而产生错误
*/
cin>>n;
getchar();//使用getchar()防止下一行读入cin的换行符
gets(ch);
getline(cin, str);
inline
关键字声明内联函数inline
关键字type * ptr = new type(val);
或
type * ptr;
ptr = new type(val);
val
将成为所申请的空间中所存储的默认值()
中不写任何值,则将初值设为0
()
省略创建方法同上,将val换成初始化列表
new ClassName
等效于new ClassName()
new ClassName
调用隐含的默认构造函数,new ClassName()
调用隐含的默认构造函数,还会将类中基本数据成员和指针成员赋初值0
,并且该约束递归作用于类的内嵌对象type * ptr = new type[len];//末尾加()可以全部初始化为0
delete ptr;
delete[] ptr;
.
、*
、::
和三目运算符?:
不可以重载,其他运算符都可以重载=
、[]
、()
和->
只能重载为类的成员函数=
运算符函数总是会隐藏基类中的=
运算符函数(使用op代指被重载的运算符)
obj1 op obj2
相当于obj1.operator op(obj2)
obj1 op obj2
相当于operator op(obj1, obj2)
返回类型 operator 运算符 (形参表){
//运算方法体
}
class A{
public:
A(int n){this->n = n;}
//重载为类的非静态成员函数
A operator + (const A &a){
return A(n + a.n);
}
int n;
} ;
//重载为非类成员函数
A operator - (const A &a1, const A &a2){
return A(a1.n - a2.n);
}
int main(){
A a1(10);
A a2(20);
cout<< a1.n <<" "<< a2.n <//10 20
//两种调用方式
A a31 = a1 + a2;
A a32 = a1.operator + (a2);
cout<< a31.n <<" "<< a32.n <//30 30
//两种调用方式
A a41 = a1 - a2;
A a42 = operator - (a1, a2);//-10 -10
cout<< a41.n <<" "<< a42.n <return 0;
}
当以非类成员函数的方式重载运算符时,有时需要访问类的私有成员,可以将运算符重载函数设为类的友元函数。可以不使用友元函数时,不要使用。
当运算符重载为类的成员函数时,函数的参数要比运算符原来的操作数少一个(后置++
和--
除外);
当运算符重载为非类成员函数时,函数的参数和运算符原来的操作数相同。
++
和--
的重载++
和--
重载为前置运算符时,运算符重载函数不需要任何参数++
和--
重载为后置运算符时,运算符重载函数需要一个int
型形参,该参数只用作区分前置运算和后置运算,没有其他作用,声明和实现函数时,都可以不给出形参名&
声明引用type var;//声明变量
type &ref = var;//声明变量var的引用
0
或NULL
表示空指针,它不指向任何地址->
对于指针和数组:
*(ptr + val) 等价于 ptr[val]
指向常量的指针
const type * ptr = &const_val;
指向常量的指针本身可以被改变,再指向其他的对象。
指针类型的常量
type * const ptr = &val;
常指针不能再指向其他对象,若其所指对象不是常量,则所指对象可以被修改
void
类型的指针可以指向任何类型的对象
声明一个函数指针,初始化
type (* ptrName)(params);
type fun(params){...}
ptrName = fun;
不加*
和&
,指针所指函数必须和指针具有相同的返回类型和形参表。
this
指向调用当前方法的那个对象自身class A {
public:
A(int a);
private:
int a;
};
A::A(int a){
//通过this消除内部变量对外部变量的屏蔽
this->a = a;
}
指向数据成员的指针
type ClassName::*ptrName;
ptrName = &ClassName::varName;
指向函数成员的指针
type (ClassName::*ptrName)(params);
以上要注意访问权限。
objName.*ptrName 或者 objPtrName->*ptrName
访问类的非静态成员不依赖对象,所以可以用普通指针访问类的非静态成员。
type *ptrName = &ClassName::varName;
0
0
int arr[len] = {1, 2, 3, ..., len};
int arr[] = {1, 2, 3, ..., len};//数组长度为len
int arr[len] = {1, 2, 3, ..., i};//i
int arr[row][col] = {1, 2, 3, ..., row * col};
int arr[row][col] = {
{1, 2, 3, ..., col},
{2, 3, 4, ...},
...,
{col, ...}
};
int arr[][col] = {1, 2, 3, ...};//可以省略行数,但不能省略列数
char str[5] = {'a', 'b', 'c', 'd', '\0'};//最后一位要放'\0'
char str[5] = "abcd";//最多存放5-1个
char str[] = "abcdef";
struct
定义public
union
定义如果结构体的全部数据成员都是public
类型的,并且没有自定义的构造函数、基类和虚函数,则可以使用如下方式直接初始化:
struct A {
int i;
char ch;
...
};
int main(){
A a = {100, 'c', ...}
}
满足上述条件的类对象也可以使用如上方式进行初始化。
template
标识的class
或typename
关键字加参数名/*函数模板的定义形式*/
template<模板参数表>
返回类型 函数名 (形参表){
//函数体
}
/*类模板的定义形式*/
template<模板参数表>
class 类名 {
//类成员
};
/*在类模板意外定义其成员函数*/
template<模板参数表>
返回类型 类名<模板参数标识符列表>::函数名(参数表){
//函数体
}
/*使用类模板建立对象*/
模板类名<模板参数表> 对象名;
virtual
关键字只能出现在函数原型声明中,而不能出现在函数定义中virtual
关键字都一样如果没有将基类的析构函数设为虚函数,在通过基类指针删除派生类对象时调用的是基类的析构函数,派生类的析构函数没有执行,因此派生类对象中动态分配的内存空间没有被释放,造成了内存泄露。
//声明虚函数
virtual 返回类型 函数名(形参表);
#include
using namespace std;
class Base0 {
public:
virtual void fun();
};
void Base0::fun(){
cout<<"Base0"<class Base1:public Base0{
public:
void fun();
};
void Base1::fun(){
cout<<"Base1"<class Drived:public Base1{
public:
void fun();
};
void Drived::fun(){
cout<<"Drived"<void ref(Base0 & r){
r.fun();
}
void ptr(Base0 * p){
p->fun();
}
void normal(Base0 b){
b.fun();
}
int main(){
Base0 b0; Base1 b1; Drived d;
//使用基类引用可以做到动态绑定
ref(b0); ref(b1); ref(d);
/**输出
* Base0
* Base1
* Drived
*/
//使用基类指针访问虚函数可以做到动态绑定
ptr(&b0); ptr(&b1); ptr(&d);
/**输出
* Base0
* Base1
* Drived
*/
//使用对象名访问虚函数不能做到动态绑定
normal(b0); normal(b1); normal(d);
/**输出
* Base0
* Base0
* Base0
*/
return 0;
}
基类名::函数名(参数表)
//声明纯虚函数
virtual 返回类型 函数名(形参表) = 0;
//如果要访问在基类中给出的纯虚函数的实现,需要使用`基类名::函数名(参数表)`
#include
using namespace std;
class Base {
public:
virtual void vfun() = 0;
virtual void fun1(){
vfun();//访问到的是派生类中的实现
}
virtual void fun2(){
Base::vfun();//访问到的是基类中的实现
}
};
void Base::vfun(){
cout<<"Base"<class Drived:public Base{
public:
void vfun(){
cout<<"Drived"<int main(){
Drived d;
d.vfun();//Drived
d.fun1();//Drived
d.fun2();//Base
return 0;
}