C++ Primer Plus学习笔记
学习资料: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;
}