《十天学会C++——范磊主讲》读书笔记

来源:互联网 发布:手机淘宝不能开店 编辑:程序博客网 时间:2024/05/17 06:19

----------------------------------------------------------------------


肯·桑普森用汇编写了第一个UNIX操作系统,他有根据马丁·理查德设计的BCPL语言为UNIX设计了另一种语言B,贝尔实验室又对B语言进行改造,形成了C语言,又因为C语言功能上面的缺陷,杨尼·斯捉司朱夫对C语言精心改进和扩充,形成了C++

80年代——1995 传统的面向对象语言
1995——2000  STL和Boost的等程序库
2000——今天  Loki,MPL等程序库为代表的产生式编程和模板元编程

C++与C语言最大的不同是解决思维方式的不同
面向结构的思维就是要将一个大程序化为若干个很小的结构,每个功能都完成一个或多个功能,所有结构集合起来就可以完成一个大功能。

C语言的缺陷:
    程序的可维护性方面,结构化导致没有考虑数据安全问题,例如游戏打怪兽
    程序的可重用性
    不支持多态性:不同的对象采用相同的方法会导致不同的行为和结果

----------------------------------------------------------------------

使用标准库函数中的cout对象输出一行信息;cin对象用来接受键盘输入
#是一个预处理标志,用来对文本进行预处理操作  iostream是一个标准库
endl具备除了具有\n的功能外,还具有刷新缓冲区的功能?(float)5/8

std::名称空间标识符, 对象cout是标准库所提供的一个对象,而标准库名字在命名空间中被定为std,这样编译器就会明白我们调用的cout是命名空间std中的cout
非标准库文件<iostream.h>,继承自C,因为当初没有命名空间这么个说法
标准库文件<iostream>为标准输入输出流,它是C++规范的带有命名空间的头文件,包含在std命名空间
using namespace std告诉编辑器我们将要使用命名空间std中的函数或对象
C++中引用命名空间的目的是为了避免和减少命名冲突

--------------------------------------------------------------------------

函数又叫做方法,即实现某项功能或服务的代码块
我们可以认为main函数是由操作系统调用的
我们通常把函数声明叫做函数原型,而把函数定义叫做函数实现
函数声明时并没有分配内存,但在函数定义时会分配内存
函数声明的目的:告诉编译器即将定义的函数的名字是什么,返回值的类型是什么,参数是什么
函数定义的目的:告诉编译器这个函数的功能是什么

----------------------------------------------------------------------------

C++六种类型:bool、char、wchar_t、int、float、double
整型:无符号型、长整型、短整型
双精度型:双精度型、长双精度型

C++从广义上来分,可以分为变量和常量
其值可以改变的量称为变量,其名字叫做变量名
其值不能改变的量称为常量,可对其初始化,但不能对其赋值,一般用const修改
每个ASCII中每个字符和标点都用一个7位二进制表示第八位如果不进行奇偶校验的话,则为0
若要强制将字符输出为ASCII值,则在输出的时候,对其进行类型强制转换(int)ch

枚举型常量可以使用文字带替代数字,如enum num{zero, one, two, three, four};

-----------------------------------------------------------------------------
语句通常由;结束,语句中的空格一般可以忽略不计
块是以左大括号{开始,以右括号}结束
表达式可以看做是用于计算值的操作,表达式总能返回一个值如:x=a+b;//a和b相加,然后把结果赋给x,同时返回x的值

一行语句中出现2个或2个以上的逻辑运算符就会产生优先级的问题,也就是逻辑运算符哪个会先执行,本书中详细讨论了使用括号修改优先级的方法

数字、符号、字母在计算机看来都是一个真值,而空字符"\0"在计算机中算一个假值

 三目运算符:a>b?a:b   ;执行方向从右向左  ;可以在不同的类型中进行操作[默认类型转换]

-------------------------------------------------------------------------------

 机器语言->汇编语言->高级语言

高级语言:Pascal, c【面向过程的语言】

面向对象语言的特征
抽象:将程序的每一部分都看做一个抽象的对象,即程序是由一组抽象的对象组成的,更复杂点,这些对象根据他们相同的特性而又组成了一个类
封装:将每个数据封装在各自的类中,设置了多个权限
继承:
多态:不同的对象,调用相同名称的函数,却可能导致不同行为或结果的现象

类:由若干个变量和相关的函数组成
对象:可拥有这些变量和函数

例如:硬盘是个类;希捷硬盘是该硬盘类的一个对象;型号,容量和转速是该类的数据成员;读取数据,写入数据是该类的方法成员(成员函数);数据成员和成员函数统称为该类的成员,对象拥有并可以封装这些成员

类是个抽象的名词,而对象则是个实际的个体

注意:
    1.类是个抽象的名词,它不是具体的某个个体,因此我们无法对它进行赋值操作
    2.对象只能调用类中存在的成员
    3.不能混淆声明和定义
    4.要使用点运算符来访问类的成员
    5.要使用对象与点运算符访问对象的数据成员,并给它们赋值
    6.不要混淆类和对象,不要给类赋值
    7.函数是用来执行一定功能的代码块,成员函数是只能被类的对象所使用的函数
    8.对象只能使用该类提供的函数,假如类没有提供某个函数,那么对象就不具备该函数

一般来说,当我们定义了一个函数后,编译器就会在内存中为其创建一个指令集,当我们仍调用这个函数式,程序就会跳转到该指令集处。而使用内敛函数“inline”,编译器不会创建真正的函数,只是将这个内敛函数的所有代码拷贝到调用函数中,而无需在指令集之间跳转,从而提高了程序运行时的效率。

例如:void print() const{cout<<"asd"<<endl;}
当把对象的函数成员函数声明为const时,则不允许修改该对象的成员变量;假如成员函数试图去修改该对象的成员变量,则编译器会提示错误

构造函数用途构造一个对象,析构函数则用于在对象被销毁后清除它所占用的内存空间,析构函数不允许有参数和返回类型;一个类只能有一个析构函数

构造函数创造一个对象,析构函数销毁一个对象

对象数组,例如A a=[2],  对象a[0],a[1]

------------------------------------------------------------------------------

------------------------------------------------------------------------------

什么是地址:
计算机要找到变量i,必须先找到i的地址,也就是i在内存中的编号;
指针是用来保存内存地址的变量

指针地址:
指针保存的地址:
该地址的值:

指针的三大用途
1、处理堆中存放的大型数据
2、快速访问类的成员数据和函数
3、以别名的方式向函数传输参数

数据在内存中的存放形式:
1.栈区【stack】由编译器自动分配并且释放,该区域一般存放函数的参数值,局部变量的值等
2.堆区【heap】一般由程序员分配释放,若程序员不释放,程序结束时可能由操作系统回收
3.寄存区【】用来保存栈顶指针和指令指针
4.全局区/静态区【static】全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在另一块区域,程序结束后由系统释放
5.文字常量区【】常量字符串放在这里,程序结束后由系统释放
6.程序代码区【】存放函数体的二进制代码

堆可以解决栈区变量寿命太短的问题和全局变量寿命太长的问题

堆是采用匿名的方式来保存数据的,只能通过指针才能访问到这些匿名的数据,同时由于堆区中的内存是由程序员来分配和释放的,所以自由度是最高的

1.内存申请方式不同【栈由系统自动分配;堆由程序员自己申请,需致命变量的大小】
2.系统响应的不同【栈的剩余空间大于申请空间,系统将为程序提供内存,否则将提示overflow,也就是栈溢出;堆会遍历操作系统的记录内存空间地址的链表】
3.空间大小的不同【栈一块连续内存的区域,大小是一个编译时就已经确定的常数,是由系统预先根据栈顶的地址和栈的最大容量定义好的;堆是不连续的内存区域串联起来的内存空间,各块区域由链内存来定的】
4.执行效率的不同【栈由系统自动分配,因此速度较快,程序员不能对其进行操作;堆由程序员分配内存,速度慢,易产生内存碎片,释放方便
5.执行函数时的不同【进栈:被调用函数下一行的内存地址->函数的参数(参数大于一,次序从右往左)->局部变量】

delete将释放掉指针所指向的内存,而不会释放指针,因此还是可以使用指针,但是不能对该指针再次删除。不过我们如果将该指针如果赋值为0的话,那么删除一个指针将是安全的

成员指针运算符“->”

一个存储在栈中的对象,在超出作用域时,会自动调用析构函数来释放该对象所占用的内存
一个存储在堆中的对象,需要程序员自行堆其所占用的内存进行释放,否则该对象程序结束后才会被系统回收

this变量记录每个对象的内存地址,然后通过间接访问所占用的内存知道运算符“->”访问该对象的成员。
this指针则指向每个单独的对象,因此不同的对象输出的this变量的内存地址也不同
默认情况下,this指针可以不写

由于this指针保存了对象的地址,因此可以通过指针直接读取某个对象的数据,this指针的创建与删除由编译器来完成,因此我们不必操心

常量指针【A *const p=new A】:其自身不可改变,但是它们指向的目标却可以改变,无论这个目标是变量还是对象

指向常量的指针【const A *p=new A】:该指针指向的目标是不可修改的,但是该指针可以被修改

指向常量的常量指针【const A *const p=new A】:它指向的目标不可修改,而且该指针也不可被修改

----------------------------------------------------------------------------------

引用变量,引用对象,不可以引用类

按值传递:函数之间会通过赋值的方式修改子程序
按地址传递:函数之间通过传输地址的方式修改子程序

我们使用别名或者指针的方式传递给函数一个以上的变量,在函数体中将需要返回的值赋给这些变量,由于使用引用或者指针传递变量允许函数改变原来的变量。因此这些在函数体中被修改的变量均可以看做已经被该函数返回的函数。

我们应该注意到,这两个值不是通过返回机制来得到的,而是通过改变函数指针参量所指向的内存区域的值来实现的

创建一个对象会自动调用构造函数
按值返回的话就会调用赋值构造函数

我们将返回值和接收参数都定义为const,就可以保证函数内不可修改原始值,同时避免利用返回值对原始值进行修改

指向常量的常指针只是限定我们用该指针修改它所指向的对象的值,但是它并不会改变原始对象的属性

指针可以为空,但引用不能为空;
指针可以被赋值,但引用只可以被初始化,不可以被赋为另一个对象的别名;
如果想要使一个变量记录不同对象的地址,那么就必须使用指针
指针可以指向堆中空间,引用不可以指向堆中空间
引用只是个别名,不能作为指针来使用

【第9章16讲】
按值返回对象时会自动调用默认拷贝构造函数来创建对象的副本
对于引用而言,如果引用的是一个临时变量,那么这个析构变量的生存期会不少于这个引用的生存期,但是指针就没有这个特性,假如对象a的副本的地址赋给一个指针,那么在func函数返回对象a的副本的时候,就可以析构这个对象a的副本
【第9章16讲】
析构函数调用并析构某个对象后,只是告诉编译器这一块内存不再为某个对象独占了,你可以访问他,别的对象也可以访问它并使用该内存区域存储它们的数据,但是在它们使用之前,存放在该内存区域的数据并没有被删除,因此可以使用指针来访问该区域仍然能够得到未被修改的值

为了避免内存泄露的问题,一般建议哪里建立堆,哪里释放。其他函数可以通过引用的方式使用该空间

---------------------------------------------------------------------------------

在函数的声明当中,void set(int =30, int =5);是正确的
一般函数重载->成员函数重载->构造函数重载

成员变量的初始化:
1.使用构造函数【实际可以理解为是对成员变量赋值的方式完成初始化工作】
2.在构造函数的函数头初始化【成员列表初始化】rectangle():length(3),width(5){cout...}【实际可以理解为是对成员变量初始化的方式完成初始化工作】
  注:其初始化顺序是按照成员列表的顺序进行的,而不是:后面的顺序
      析构函数会最先析构最后一次运行的构造函数,个人感觉类似于入栈和出栈的顺序

每个对象在创建时都要调用构造函数来为自己初始化,如果自己没有定义构造函数,那么编译器会自动创建一个默认构造函数,假如用户自己创建了一个构造函数,无论是否带有参数,编译器将不再提供任何默认构造函数。

delete 会自动调用析构函数释放掉new 创建的对空间,可以认为一旦执行到delete后,便会自动调用析构函数

我们有时可以把数字当做对象赋值给另一个对象,这样在该对象赋值表达式进行计算时,首先要对数字进行类型转换,同时判断该类的构造函数的参数是否与数字的类型匹配,键入匹配则调用构造函数创建一个临时对象,接着将该临时对象赋值给赋值操作符左边的对象,最后调用析构函数删除临时对象【第10章11讲】

拷贝构造函数浅拷贝和深拷贝的问题,个人认为实际上就是在指针指向的堆是否删除的问题,比如在一个类中创建一个堆,并且使用一个指针*p指向它,当拷贝后,在新的堆中也会出现一个指针*p(为了便于区分,暂时称作*p吧),这样*q和*p会指向同一个堆,【因为堆是构造函数创建的,而*q是拷贝构造函数创建的,所以结果是只有一个堆,两个指向该堆的指针】
浅拷贝中,当*p调用delete释放掉创建的堆时,*q此时也指向这个堆,这样做的结果就是*q会变成野指针。
深拷贝中,在拷贝构造函数中再次创建一个堆,这样两个指针就指向了不同的堆,调用delete就不会影响到另一个指针

---------------------------------------------------------------------------------

C++中的任意算数运算都是通过调用函数来实现的,因此运算符重载的时候必然会调用operator**函数

C++在operator=,也就是“=”运算符重载的过程中调用的是浅拷贝,故如果在类的成员中如果有指针,则程序运行会崩溃,但编译应该不会有问题

C++中大部分的运算符可以被重载,但有一部分却不能被重载

.  ::  ^  #  ?:都不能被重载

---------------------------------------------------------------------------------

我们把这种在原有类的基础上建立新类并且添加新特征的过程叫做“类的派生”;把原有的类叫做“基类”,又叫“父类”;把新建的类叫做“派生类”,又叫做“子类”

C++有两种继承,单一继承和多重继承:
只有一个基类的叫单一继承,拥有多个基类的叫做多重继承

派生类可以访问基类的公有成员,但不能访问基类的私有成员

protected对于其他类不能访问其成员,但派生类却可以访问该成员;应该注意到父类中的保护成员,被继承来之后仍然是保护成员。
private类型中的成员,其派生类也不能访问

我们可以将派生类的对象赋值给基类的对象,但反过来就会出错

若派生类以私有继承的方式继承基类,那么基类的public成员和protected成员在派生类中都是私有的,如果想要访问,那么就必须提供接口函数。
同时须注意到无论是公有继承还是私有继承,基类的private成员在其派生类中是不可访问的。

在创建派生类的构造函数时,一般有两种方法对其进行初始化
1.在派生类中创建一个构造函数,然后初始化所有数据(从基类哪里继承来的数据和子类的数据)。
  注:这种方法显然多余因为基类已经有了恰当的构造函数。故不可取
2.在派生类中创建一个构造函数,用该构造函数调用基类的构造函数并且向构造函数传递初始值
  注:如果没有定义派生类的构造函数,那么将默认执行基类的构造函数
      如果派生类要向基类传递参数,那么必须在派生类里定义一个构造函数,该参数只起到向基类传递参数的作用
      如果只需要调用基类的构造函数,不用向基类传递参数的话,那么派生类不用定义构造函数

------------------------------------------------------------------------------------

father *pfather=new son
在堆上创建了一个新的son对象,并且返回指向该对象的指针,而赋值操作=又将该指针赋给指向father的指针。
这样的好处是son对象可以直接访问father的数据和函数

在函数前面加关键字virtual,表示该函数是有多种形态的,即该函数可能被多个对象所拥有,而且功能不一,
话句话说多个对象在调用同一名字的函数时产生的效果也不一样,那么系统在执行到有关键字virtual的函数时,会自动判断是哪个对象调用了它,然后调用该对象的同名函数

程序不知道我们要预先调用哪个对象的函数,指针p使我们输入一个数时才指向某个对象的,这就叫做动态联编
如果运行前就已经确定了指针将要指向哪个对象,而且在运行时不能改变的叫做动态联编
动态联编可以动态跟踪对象,灵活性比较强,但速度浪费严重;
静态联编不能动态跟踪对象,但速度快

如果一个函数被说明为虚函数,在派生类中覆盖了该函数;
也可以理解为派生类中的同名函数同样为虚函数

将一个调用函数者联结上正确的被调用的函数,这一过程叫做函数联编,一般简称联编;

降入我们在虚函数中没有采用指针或者引用,那么就无法实现动态联编

一般情况下任何类的析构函数都可以声明为虚析构函数,当指针被删除操作时,系统就会获得对象执行时的类型,并调用正确的析构函数。
一个派生类在创建时会首先调用基类的构造函数,然后调用该类的构造函数。
一般情况下,在使用虚构函数的时候,我们都会将派生类对象传递给指向基类的指针。
指向派生类对象的指针删除时,如果析构函数是虚函数,则会先调用派生类的析构函数,而派生类的析构函数会自动调用基类的析构函数,因此构造的整个对象都会被销毁

1.因为析构函数不允许与参数,因此它不可能实现重载
2.只要基类的构造函数被说明为虚函数,那么派生类的析构函数无论说明与否,都自然成为虚函数。
3.在C++中虚构造函数是不存在的,因此无法声明

建议:
如果基类中定义了虚函数,那么析构函数也应该说明为虚函数,这样对内存的回收会更准确些。

----------------------------------------------------------------------------------
----------------------------------------------------------------------------------

在程序运行时不能动态的对各个节点进行创建,添加,和删除的工作,这都是在云性侵案已经确定好了的,我们只能在编译前修改指向节点的指针,这种链表叫做“静态链表”

在C++的对象中,因为对象的产生要调用构造函数,消亡需要调用析构函数。malloc和free函数无法满足这两个基本要求

----------------------------------------------------------------------------------
father *pf=new son;
父类中并没有beautiful()这个函数
dynamic_cast<son *>(pf)->beautiful();

dynamic_cast是RTTI中的一个方法,RTTI是"Runtime Type Information"的缩写,意思是运行时类型信息,它提供了运行时确定对象类型的方法。
它有两个方法,分别是typeid和dynamic_cast
如果想要程序正确运行的话,需要修改项目属性。一般不建议使用
在程序中应当尽量少用RTTI,因为RTTI是硬性的通过将基类指针转换为派生类指针来达到方位派生类函数的目的,它实现的并不是多态性。

真正的抽象类必须具备一个或一个以上的真正没有任何功能的虚函数,这个虚函数的存在仅仅是为了让它的子类来继承并具体化功能。

一个虚函数通过初始化为0就变成了纯虚函数,这个虚函数是彻底无任何功能的,不能直接调用它,只有被子类继承并赋予新功能后才能被使用

一个类可以有多个纯虚函数,包含有纯虚函数的类叫做抽象类

抽象类中定义的纯虚函数并没有任何作用,派生类将这些纯虚函数继承过来后给予其不同的功能。
而多态性有保证了指针调用不同的虚函数
抽象类并不是实际存在的类,所以不能定义一个抽象类的对象,但可以定义一个指向抽象类的指针。

一个抽象基类仍然可以派生出抽象类,只要该类没有把纯虚函数全部覆盖到,那么该派生类仍然是抽象类
假如确定某个基类的虚函数一定会被其所有的派生类覆盖掉,那么可以考虑将其设置为纯虚函数

虽然多重继承比单一继承有很多优点,但是由于虚基类的定义很复杂,代码容易产生多义性,造成调困难
因此一般情况下,单一继承可以实现,尽量不要使用多重继承

---------------------------------------------------------------------------------------

包含对象与嵌套类有着本质的不同,包含对象只是将另一个类的对象作为该类的成员,而嵌套类则是在该类中定义了一种新类型,这个类型只能在该类中使用
由于嵌套类作为一种自定义的数据类型被封装在另一个类中,因此可以避免与其他类的名称冲突
0 0
原创粉丝点击