首页

源码搜藏网

首页 > 开发教程 > 软件工程 >

C++易错知识点整理[不定期更新]

创建时间:2016-06-14 17:43  

目录

构造与析构

构造函数

析构函数

复制构造函数

类的复制构造函数在以下情况被调用:

深复制和浅复制

声明和实现复制构造函数的一般方法:

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(...), ...{
    //构造函数主体
}

conststatic

常对象

常成员函数

常数据成员

class A {
public:
    A(int i);
private:
    const int a;
};
//在初始化列表中初始化常量
A::A(int i):a(i){
    //构造函数的其他内容
}

类中静态变量和常量的初始化

常引用

//下面的做法是错的,不能重复设置默认值
void fun(int a = 1, int b = 2);
int main(){

}
void fun(int a = 1, int b = 2){

}

异常

catch后的异常类型可以不声明形参,但这样无法访问所捕获到的异常对象。
使用不带操作数的throw可以将当前捕获的异常再次抛出,但是只能在catch块或catch块中调用的函数中使用。
若异常抛出点本身不在任何try-catch块内,则结束当前函数的执行,回到当前函数的调用点,把调用点作为异常的抛出点,然后重复这一过程。

/*throw表达式语法*/
throw 表达式 ;

/*try-catch块语法*/
try {
    //可能发生异常的内容
} catch (异常类型1 形参名) {
    //捕获到异常后的处理方法
} catch (异常类型2 形参名) {
    //将当前捕获到的异常再次抛出,将抛出源异常对象,而不是其副本
    throw;
} catch (...){
    //使用...捕获所有类型的异常
}

异常接口声明

如果函数抛出了异常接口声明中所没有的异常,unexpected函数会被调用,该函数默认会调用terminate函数中止程序。用户可以自定义unexpected函数的行为。

/*在函数声明中说明函数可以抛出哪些异常*/
返回类型 函数名(参数表) throw (异常类型1, 异常类型2, ...);

/*不抛出任何异常的函数*/
返回类型 函数名(参数表) throw ();

异常处理中的构造与析构

发生异常时,从进入try块(捕获到异常的catch所对应的那个)直到异常抛出前,这期间栈上构造的并且没被析构的所有对象都会被自动析构,这一过程被称为栈的解旋

抛出string类型的异常的注意事项

当抛出的异常为string类型时,要注意不能直接抛出匿名字符串,而要先声明一个string类型的变量或常量,然后再将该变量或常量抛出。

void fun() throw (string) {
    ...
    const string str = "msg";
    throw msg;
    //throw "msg";//这样写的话捕获不到异常
}

类的继承与派生

//使用基类名限定
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 &param):Base(param), ...{
    //派生类复制构造函数体
}

在定义类之前使用该类,需要使用前向引用声明。
在提供类的完整定义之前,不能定义该类的对象,也不能在内联成员函数中使用该类的对象。

class B;//前向引用声明

class A {
    ...    
    B b;//错误!类A不完整,不能定义它的对象
    B &rb;//正确
    B *pb;//正确
};

class B {
    ...
};

使用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);

类的内联成员函数

动态创建的数据与对象

动态创建基本类型的变量

type * ptr = new type(val);

type * ptr;
ptr = new type(val);

动态创建类的对象

创建方法同上,将val换成初始化列表

动态创建数组类型的对象

type * ptr = new type[len];//末尾加()可以全部初始化为0

删除动态申请的内存

delete ptr;
delete[] ptr;

运算符重载

运算符重载规则

运算符重载的两种形式

(使用op代指被重载的运算符)

返回类型 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 <<endl;//10 20
    //两种调用方式
    A a31 = a1 + a2;
    A a32 = a1.operator + (a2);
    cout<< a31.n <<" "<< a32.n <<endl;//30 30
    //两种调用方式
    A a41 = a1 - a2;
    A a42 = operator - (a1, a2);//-10 -10
    cout<< a41.n <<" "<< a42.n <<endl;
    return 0;
}

当以非类成员函数的方式重载运算符时,有时需要访问类的私有成员,可以将运算符重载函数设为类的友元函数。可以不使用友元函数时,不要使用。
当运算符重载为类的成员函数时,函数的参数要比运算符原来的操作数少一个(后置++--除外);
当运算符重载为非类成员函数时,函数的参数和运算符原来的操作数相同。

对于++--的重载

需要重载为非类成员函数的情况

引用、 指针和数组

引用

type var;//声明变量
type &ref = var;//声明变量var的引用

指针

函数指针

声明一个函数指针,初始化

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 *ptrName = &ClassName::varName;

数组初始化

int arr[len] = {1, 2, 3, ..., len};
int arr[] = {1, 2, 3, ..., len};//数组长度为len
int arr[len] = {1, 2, 3, ..., i};//i<len,后续元素自动初始化为0

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";

结构体和联合体

结构体

联合体

结构体成员初始化

如果结构体的全部数据成员都是public类型的,并且没有自定义的构造函数、基类和虚函数,则可以使用如下方式直接初始化:

struct A {
    int i;
    char ch;
    ...
};

int main(){
    A a = {100, 'c', ...}
}

满足上述条件的类对象也可以使用如上方式进行初始化。

模板

函数模板

/*函数模板的定义形式*/
template<模板参数表>
返回类型 函数名 (形参表){
    //函数体
}

类模板

/*类模板的定义形式*/
template<模板参数表>
class 类名 {
    //类成员
};

/*在类模板意外定义其成员函数*/
template<模板参数表>
返回类型 类名<模板参数标识符列表>::函数名(参数表){
    //函数体
}

/*使用类模板建立对象*/
模板类名<模板参数表> 对象名;

虚函数与运行时多态

虚函数

如果没有将基类的析构函数设为虚函数,在通过基类指针删除派生类对象时调用的是基类的析构函数,派生类的析构函数没有执行,因此派生类对象中动态分配的内存空间没有被释放,造成了内存泄露。

运行时多态的条件

//声明虚函数
virtual 返回类型 函数名(形参表);
#include<iostream>
using namespace std;

class Base0 {
public:
    virtual void fun();
};

void Base0::fun(){
    cout<<"Base0"<<endl;
}

class Base1:public Base0{
public:
    void fun();
};

void Base1::fun(){
    cout<<"Base1"<<endl;
}

class Drived:public Base1{
public:
    void fun();
};

void Drived::fun(){
    cout<<"Drived"<<endl; 
}

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<iostream>
using namespace std;

class Base {
public:
    virtual void vfun() = 0;
    virtual void fun1(){
        vfun();//访问到的是派生类中的实现
    }
    virtual void fun2(){
        Base::vfun();//访问到的是基类中的实现
    }
};

void Base::vfun(){
    cout<<"Base"<<endl;
}

class Drived:public Base{
public:
    void vfun(){
        cout<<"Drived"<<endl;
    }
};

int main(){
    Drived d;
    d.vfun();//Drived
    d.fun1();//Drived
    d.fun2();//Base
    return 0;
}
1
0
   
上一篇:<LeetCode OJ> 318. Maximum Product of Word Lengths
下一篇:[数据结构] 图的BFS、DFS、prim、Dijkstra算法(Java代码)

相关内容

热门推荐