学习资料:C++ Primer Plus(中文第六版)

 第一章

介绍c++起源,暂时跳过

第二章

  • 1.只有main()可以默认返回return 0,其他函数不行,否则有可能会造成编译错误
  • 2.c++源文件在不同的编译环境下有多种扩展名,例如C、cc、cxx、c、cpp
  • 3.名称空间:相当于封装一个程序,在出现多个程序拥有相同名字的函数、变量名时,编译器不会混乱
  • 4.声明变量:对应变量所占空间(数据类型),名字。不容易造成混乱。c++风格在首次使用变量的地方声明它,好处在于在程序中找变量,坏处是无法一目了然某程序用了哪些变量
  • 5.cin,cout(都是类) 能够智能识别 变量的数据类型
  • 6.int void return double是C++的关键字,main甚至cin cout都可以作为变量名,但不能够 变量 和 输出混用

第三章

1.关于变量名

  • 字母下划线开头
  • 不能以 __ 和 _大写 组成,这些要编译器来用

2.C++如何确定cout常量的类型:先以int为主,存不下用long long 

.

.

鉴于时间考虑,已经会的章节就不看了。直接跳到第八章

第八章

内联函数

  • 相当于直接把函数内容复制到调用的地方,节省了程序跳转的时间,但同时带来了空间开销
  • 不能递归

引用

  • 引用变量只能在初始化的时候赋值
  • 引用具有const属性
  • 不能从函数中返回即将释放的引用变量(内存)
  • ostream类
  • width方法仅生效一次
  • 值引用会降低程序速度,const指针和vonst引用,可以避免创立中间变量
  • 传递类标准的方式是引用传递

 

 

p274  cin使用引用....(不懂)

默认参数

  • 只能从右向左赋值形参表


重载函数

  • 非const给const赋值是合法的


函数模板

  • class和typename是等价的template<typename 自定义>


隐式示例化,显示实例化,显示具体化区别

  • 模板;模板示例;不用模板,针对某种结构进行自定义操作


编译器选择重载函数:

  • 完全匹配
  • 提升转换(char,short → int,float→double)
  • 标准转换(int → char , long → double)
  • 用户定义的转换


关键字decltype

  • 解决模板函数定义 不同数据类型算术 变量的难题


头文件包含的内容

  • 函数原型
  • const define
  • 结构体,类声明
  • 模板函数
  • 内联函数


头文件写引号和尖括号的区别

ifndef的作用

  • 使得一个头文件不会被包含两次,原理是忽略除第一次保护外的所有内容

第十章



接口

  • 接口是一个共享框架,供两个系统交互使用。例如类中的函数成员

类的属性

  • 封装
  • 数据隐藏:将数据放在类的private中

Private

  • class默认private,不写也没关系
  • 类函数成员被当作是内联函数
  • 每一个类方法只有一个存储地方,即使定义多个类


构造函数

  • 初始化新建的类
  • 没有返回值
  • 隐式/显式调用构造函数
  • 默认构造函数始终存在,如果没自定义可以不写,自定义了一定要写。倘若是后者,就必须定义默认构造函数,构造有2种方法。第一,自定义构造函数提供所有默认值;第二,无参数构造函数


析构函数

  • 如果new的class,会用delete来释放内存。否则会有隐式的析构函数
  • 声明方法:~类名
  • 程序中使用两对大括号,使得析构函数在程序结束之前得到调用


this指针

  • 指向调用对象,返回*this (*解除运算符)


P368有错误

  • 常量作用域为类,用static声明(所有实体共用一个变量)或者枚举的方式,因为类不声明变量。

第十一章

友元函数

  • friend关键字声明,必须在类里面声明友元函数,代表该类和哪个函数友元

关于重载<<

  • 假设我们要出一个类对象trip,我们可以调用成员函数trip.show()显示其内容。我们也可以重载<<。但如果定义为成员函数,则写法变成trip<<cout(trip 调用 cout里所有的方法,因为此时其是成员函数,肯定要对象调用方法,总结一下格式),定义成友元 就是cout<<trip。注意一下成员函数调用,和友元函数调用时的区别
  • <<运算符要求左边是一个ostream引用对象,因此在上例写友元函数时,返回一个ostream引用对象
  • 总之,对于重载运算符,注意一些使用规则,和实现的两种方法,对于友元函数要用到this指针,但友元的灵活性更大
  • 切记不可因为重载或者友元函数产生二义性
  • 一个函数后跟上const声明调用该函数不会对对象修改任何东西
  • 当自己定义一个了一个名称空间x后,如果后面要用到该空间x的变量(因为不同空间可以定义同名函数变量),就需要加上x::再用

类的转换

  • 补充常识:1英石=14磅
  • 显式:人为主动传参数或者进行其他操作
  • 隐式:系统内部自动进行的操作或者自动产生的变量
  • 若使用explicit声明构造函数,则会关闭隐式转换,只能主动调用显式转换
  • 编译器碰到二义性的问题时,会报错。因为它无法决定哪一个可选,可选哪一个更优
  • 只带有一个参数的构造函数,可以直接把对应的变量复制给类对象(隐式转换)
  • 尽量使用显式转换!
  • 用友元函数传入2个变量来做类的加法运算能让程序更健壮,理由是碰到double+class的情况,成员函数无法解决,只能用友元函数(当然,前提是在class有只需要double赋值的构造函数存在)。也可以让友元函数重载,只是要写好2次,麻烦

第十二章 类和动态内存分配

对类成员使用内存动态分配

  • 静态类成员只会被创建一次,被共享使用
  • ::作用域运算符
  • 实现深复制而不是默认复制(浅复制),浅复制只能表面复制,深复制可以有更多优化的地方(不只是数据的复制,还有地址的不同),需要自定义深复制。
  • 复制构造函数和赋值运算符不同在于,前者用于初始化,会产生新的变量。后者不会产生新变量。

隐式和显式复制构造函数

  • 隐式是默认的
  • 自定义显式构造函数能够实现更多的功能,例如计算cnt

隐式和显式重载赋值运算符

  • 重载赋值运算符后,可以进行深复制,即不会共用同一个地址,共用同一个地址很可能会发生问题(隐式重载运算符的危害)


使用静态类成员

  • 静态类成员函数只能使用(返回)静态函数成员变量,使用范围比较窄

将定位new运算用于对象

  • delete [] p  只能作用于p=nullptr or p=new char[N],即new和delete必须互相兼容。若有多个构造函数,new的方式一定要和唯一的析构函数相兼容


关于返回对象

  • 若参数都是const引用,返回const引用可以不产生变量(即不调用复制构造函数),提高程序效率


队列模拟

  • 初始化和赋值不是一个概念
  • 引用和const只能在初始化的时候赋值
  • 若在class中有const,那么只能用构造函数的成员初始化列表语来赋值(在c++11前)
  • 但是C++11后,可以在类内赋值

第十三章 类继承

基础知识

  • 派生类,基类
  • 在调用构造函数的时候,一定会产生新的变量,因为类中只是声明了怎么分配内存和变量。初始化使用 成员初始化列表 可以减少 赋值 的过程
  • 创建派生类对象时,程序首先调用基类构造函数,再调用派生类构造函数。派生类对象过期时,先调用派生类析构函数,再调用基类析构函数
  • 可以把继承类对象赋值给基类引用,因为基类的构造函数一定对继承类对象有用。反之,继承类引用不能被基类对象赋值,因为继承类构造函数会有其他成员变量,不能对基类赋值。
  • 继承类存在隐式构造函数,可以将基类转化成继承类。也可以反向,将继承类对象赋值给基类对象,这样会调用隐式重载赋值运算符,只将继承类的基类部分赋值给基类对象
  • 基类的构造函数如果默认,那派生类必须自定义构造函数。如果不默认,那问题不大,因为先调用基类构造函数,还是取决于不同的代码

is-a关系的继承

  • is-a-kind-of


多态公有继承

  • 方法 行为取决于调用该方法的对象
  • 实现方法:虚函数,重新定义基类方法(覆盖)


向上和向下强制转换

  • 向上:继承类到基类
  • 向下:基类到继承类

虚成员函数

  • 如果使用了virtual关键字声明了同名函数,则引用或者指针会进行理想中的选择。
  • 方法在基类中声明了虚方法,在继承类中自动转化为虚方法
  • 虚析构函数保证了析构顺序正确性
  • 给基类函数定义虚析构函数,可以让派生类在结束时,调用自己的析构函数,再调用基类析构函数,有利于空间完全释放
  • 友元函数不是虚函数,因为它不是成员函数
  • 在派生类中重新定义不同的基类虚函数,将会覆盖基类虚函数。不过好像没什么关系?
  • 哪些函数不能是虚函数:普通函数,构造函数,友元函数,内联函数,静态函数


静态联编与动态联编

  • 编译时;
  • 运行时,虚方法(基类通常会加虚方法);

访问控制
让派生类可以使用基类的protected定义的变量


纯虚函数

  • 虚函数后面加 = 0,代表纯虚函数,不能创建该类

第十四章 c++中的代码重用
基础知识


包含对象成员的类

  • 是什么:相当于类里面有一些其他类的实例化对象
  • 什么用:实现has-a
  • 如何实现:直接用

私有继承 

  • 是什么:声明了当前类继承了某些类。和包含不同的是,它不声明对象,只声明了类。
  • 什么用:实现has-a
  • 如何实现:直接用

包含和私有继承

  • 一个是包含,一个是派生。那么注定,只有派生能用的,包含不能用。公有继承和私有继承区别于继承方式,公有保持不变,私有的派生类(基类都是私有变量)。暂时不能很好地体会私有继承的优点。
  • 保护继承:让派生类的派生类,能直接使用父类的protect成员。感觉很鸡肋啊。。我始终可以用本身的公有函数去使用私有成员。

 

第十五章 友元、异常和其他

 

  • 友元
  • 假设B是A的友元类,定义顺序无关;若指定B中的某一个函数作为A的友元函数,因为在A中要声明友元函数的名字
  • class A
  • class B{}
  • class A{}这样定义才可以。
  • 和class自己定义友元函数相比较,其可以将定义放在后面。而以其他类的某个函数为友元函数时,必须友元类定义在前面。疑惑!!!
  • 但是不能A的部分是B的友元,B的部分是A的友元

其实都只声明方法应该是最好的方法,等晚上回去上机试试


嵌套类

  • 在类中嵌套类,具体作用:减少变量名冲突的可能性,因为作用域仅在类内

异常

  • try 通过 throw 到达 catch
  • 栈解退有利于完全释放空间


RTTI

  • 被口诛笔伐?那可以pass了,一定会有取而代之的实现方式。PASS

类型转换运算符
dynamic_cast实现继承类向基类的转换
const_cast实现常量和变量的互相转换,但是不能跨数据类型。

throw和return的区别

  • throw到上一个try,而return到上一次调用它的地方


智能指针

  • 所有只能指针类只能进行显式转换
  • unique_ptr 右值是临时变量可以赋值,长期变量不行
  • share_ptr 引用计数,仅当计数==0的时候才delete指针
  • auto_ptr<type> x(new typr)

 

总结:

在看C++ Primer Plus之前已经学过许多关于c++的东西,没有系统地学过。看得过程中跳过了大部分已经学过的东西,主要学的知识是关于类,还有运算符重载。有很多细节的知识没实践过容易遗忘,保留这篇博文,方便以后自己查阅。

今天把学过的知识串起来再看一遍,把几个例题做掉。C++学习暂时就算告一段落。

 

Problem 1

p423实现第七题,实现complex类尝试使用友元函数和成员函数解决。重载+=,-=
 

#include<cstdio>
#include<iostream>
typedef long long ll;
using namespace std;
class complex{
private:
    int img;
    int real;
public:
    complex(){img=0;real=0;}
    complex(int a,int b){img=a;real=b;}
    complex operator+(const complex & t) const {
        complex now;
        now.img=img+t.img;
        now.real=real+t.real;
        return now;
//        return complex(img+t.img,real+t.real);
    }


    complex operator-(const complex & t) const {
        complex now;
        now.img=img-t.img;
        now.real=real-t.real;
        return now;
    }
    complex operator*(const complex & t) const {
        complex now;
        now.img=img*t.img-real*t.real;
        now.real=img*t.real+real*t.img;
        return now;
    }
    complex operator~() const {
        complex now;
        now.img=img;
        now.real=-real;
        return now;
    }
    void show(){cout << real<<" "<<img << endl;}
    friend void operator +=(complex &a,complex &b);
    friend complex operator*(double m,const complex &t);
    friend ostream & operator<<(ostream &os,complex &x);
    friend istream & operator>>(istream &os,complex &x);
};

void operator += (complex &a,complex &b){
    a.img=a.img+b.img;
    a.real=a.real+b.real;
}

istream & operator>>(istream &is,complex &x){
    is >> x.real >> x.img;
    return is;
}
ostream & operator<<(ostream &os,complex &x){
    os << x.real <<" "<< x.img;
    return os;
}

complex operator*(double m,const complex &t){
    complex now;
    now.img=m*t.img;
    now.real=m*t.real;
    return now;
}

int main(void){
    complex t1,t2;
    cin >> t1 >> t2;
    t1+=t2;
    t1.show();
    cout <<"Over Loading cout"<<endl;
    cout << t1 << endl;


    return 0;

}

Problem 2

尝试派生类和基类。 重写情况(函数不同)下,不同引用赋值的调用情况。以及实现虚函数virtual不同引用的情况

#include<cstdio>
#include<iostream>
typedef long long ll;
using namespace std;

class A{
private:
    int x,y;
public:
    A(int a,int b){x=a;y=b;}
    virtual void show(){cout <<x <<" o "<< y << endl;}
};


class B : public A{
private:
    int xx;
    int yy;
public:
    B(A &c):A(c),xx(0),yy(0){} /// 注意,当继承类定义构造函数要考虑基类函数
    void show(){cout <<xx<<" x "<<yy<<endl;}
};

int main(void){
    A t1(1,2);
    B t2(t1);
    A * p=&t2;
    p->show();
    /// 如果没有virutal,一切按照指针的类型来进行,由于有了virtual,使得基类指针要考虑赋值对象的类型,考虑是否子类有虚函数的存在
    return 0;

}

Problem 3

explict的应用

#include<cstdio>
#include<iostream>
typedef long long ll;
using namespace std;

class A{
private:
    int x,y;
public:
    /// 没有explicit时可以进行隐式赋值
    explicit A(int a){x=a;y=0;}
    virtual void show(){cout <<x <<" o "<< y << endl;}
};



int main(void){
    A t=2;


    return 0;

}

Problem 4

class A,class B。指定B中某个成员函数成为A的友元函数

#include<cstdio>
#include<iostream>
typedef long long ll;
using namespace std;
class A;

class B{
public:
    B(){}
    void show(A &t);
};


class A{
private:
    int x,y;
public:
    friend void B::show(A & t);
    A(int a,int b){x=a;y=b;}
};

inline void B::show(A & t){
    cout << t.x <<" "<<t.y<<endl;
}

int main(void){
    A t1(2,3);
    B t2;
    t2.show(t1);

    return 0;

}