c++ primer plus读书笔记

前言:

      接触面向对象已经两年了,但总有许多困惑萦绕心头。于是拜读了c++ primer plus,解决了我心中的许多困惑。记录以下:

 

运算符重载和函数重载:

<<输出运算符,又可以当做左移运算符,他是通过重载实现的,同一个运算符,将有不同的含义,编译器通过上下文来确定运算符的含义。如c中:&既可以表示地址运算符,又可以表示接位AND(逻辑)运算符,*即表示乘法,又表示对此生解除应用,重要的不是这些运算符的具体功能,而是同一个符号可以有多种含义,而编译器可以根据上下文来确定其含义,这就是重载。重载其实是由编译器来实现的

 

 

重载函数:相当于英语里的一词多义:如present:现在,礼物,赠予。这种词需要根据上下文来确定语义。多态函数的语义是编译器根据上下文环境来确定的。(参数个数,参数类型来确定要调哪个)。多态函数,在编译前他们的函数名是相同的,但是编译后他们的函数名是不同的。因为编译器会根据上下文(形参个数,形参类型)对每个多态函数名进行加密。

int f(int a).......int g(char b).....加密后?f@@intx

和?g@@charx

 

c++前向引用声明:

https://blog.csdn.net/zrh_CSDN/article/details/81366213

 

类描述了一种数据类型的全部属性(包括可使用它执行的操作)

 

命名空间:

c语言的标识符在<xx.h>头文件里,这些标识符使用的是全局命名空间。c++为了区别c,使用了不带.h的头文件<>。该文件没有定义全局命名空间。所以c++库里的东西在std空间下。using namespace std相当于导入c++标准库里的东西using namespace std;

这样命名空间std内定义的所有标识符都有效(曝光)。就好像它们被声明为全局变量一样。

命名空间:一句话。就是为了让编译器能准确的导入类,导入标识符。否则编译器就不知道是要用a中的cout呢,还是b中的cout,原来学生的命名是某学校的小明同学,后来该学校又来了一个叫小明的同学,所以学校的学生的命名制度又变了。某学校某班的小明同学(同名字的不能被分到同一个班~~否则编译器会报错)

 

结构体和共用体:

结构体采用补白(内存对齐方式)存储数据

共用体:共用体类型所占内存空间的大小取决于其成员内存空间最多的那个成员变量,在每一瞬间起作用的成员就是最后一次被赋值的成员,正因如此,不能为共用体的所有成员同时进行初始化,只能对第一个成员进行初始化,此外,共用体不能进行比较操作,也不能作为函数参数

结构体:人<int 婚姻状况标记,共用体:婚姻状况{int 未婚,结构体:已婚#结婚日期,子女数量....#}>

枚举:

枚举类型的变量只能是int或long类型的值。未赋被值的枚举量的值是零,后面没有被初始化的枚举量的值将比前面的枚举量大一

enum bits{one..two=3..three}

bits myflag;

myflag=bits(6)值为6~~~int a=myflag.three值为4

 

取值运算符和逻辑运算符&

句点运算符.

 

指针与所指向的值相当于硬币的正反面

指针本质

指针本质是地址。存储一个指针,还32位系统上需要4字节,64位上是8字节(它的内存大小只取mi决于{编译器,操作系统,cpu的位数}~~~~三者之中谁的位数最小,指针的内存大小就取最小位数所对应的字节大小)

char a='hi'    char *p=a;只能说p指针指向的是字节为1的char类型的变量

 

new运算符(用于动态分配内存)

strut Person{int age,int height};

Person p;   p.age=3    ~~~~静态分配结构体内存

Person *p2=new Person;    *p2->age=3~~~~~~~动态分配结构体内存

 

静态运行和动态运行。

静态运行的意思是说,在编译的时候就分配好了所有的内存和指定好了,哪个对象要调用哪个函数?

动态运行是指在程序运行的时候动态的去分配内存和确定了哪个对象要调用哪个函数?

个人理解的c++的内存模型:

他将内存分为两部分,一部分是静态内存,他专门用于存储静态变量和自动变量,而这个内存是在编译的时候就已经分配好了,另一部分是动态内存,他又叫内存池,或者堆,然后它是在运行的时候,程序运行的时候,才开始为变量分配内存

 

内存泄漏

使用new运算符在堆上分配好内存之后,但是没有delete,所以导致了该块内存一直不能被使用,从而拖垮应用程序可用的内存。一般程序执行完都会把堆上的内存全部清空释放,但是对于安卓程序,程序是一直在运行的,所以就导致该内存一直不会得到释放。还有像服务器这样的程序,他一天24小时都不会关机,并且一直都在运行,如果发生内存泄露,这是一件非常可怕的事情

 

数组,模板类vector和模板类array

vector对象存储在堆中{动态内存},array对象和数组存储在栈中(静态内存)

vector对象的长度是可变的,元素中可含变量{变长数组的替代}

array和数组长度是不可变的,元素中不可含变量{定长数组的替代}

 

isalpha(ch)ch是字母?isdigit()数字,ispunct标点,tolower()转小写.....

 

<cstring.h>中有strlen获取字符串的长度

 

函数通过将返回值复制到指定的cpu寄存器或内存单元将其返回,随后调用程序查看该内存单元

 

函数和函数指针以及指针函数

函数的地址是存储其机器语言代码的内存的开始地址

 

double (*pf)(int)这里pf是指向函数的指针。该函数返回double类型,需要传一个int型的参数

 

double *pf(int)这是的pf指函数的返回类型为指针

 

pf和*pf等价~~~既是函数名,又是函数的地址。

 

函数里存的是代码块,就是存了一段代码。所以函数是对编译单元,是对程序代码的分割。每个函数都有一个地址。一个函数就是一段代码段{引用汇编里的术语:代码段}

 

引用变量

        它是已定义变量的别名(另一个名称而已)

主要用作函数形参。这样函数使用的就是原始数据,而不是复制后的副本。引用是一种复合类型。

 

编译

编译的最终产品是可执行的二进制程序{机器指令}

运行程序。计算机会将这些指令入到内存。因此每条指令都有一个内存地址。计算机逐步执行指令。有时碰到诸如函数这样的东西,会暂时跳过一些指令。等执行完被调函数的指令后再接着原来的地方继续执行指令。调函数的时候,计算机先将参数复制到栈。然后被调函数从栈中取数据完成自己的事情。他也会把返回值存到内存或cpu的寄存器中

 

引用和指针

引用:声明和初始化是同时的

指针:可以先声明,再初始化

int &myname=name 这里&是类型标识符,是引用

int * const pm=&name 这里的&是取址运算符

 

说白了,引用就是const指针的另一种表示形式

 

按引用传参还是指针传参:

1.参数是类对象:类对象的标准是引用传递

2.数据对象是数组:只能使用指针

3.数据对象很小:按值传递(如基本类型数据)

4.数据对象是较大的结构,则使用const常量指针或常量const引用,以提高效率,减少复制结构所需的时间和空间

 

默认参数:

int defaultfun(int n,int m=4)..这里n的值为1,该机制只对函数原型有效

 

函数模板:

它是通用编程的一种,用泛型来代表通用类型。其实它是语法糖。类似伪代码。最终它都会被编译根据环境进行函数参数类型的具体化。即编译器做了类型转换的工作。而不用我们来做了

 

ambiguous二义性:

即编译器慌了,不知道要调哪个函数

 

显式实例和隐式实例:

template<>void Swap(int &,int &);(swap函数按引用传递)

或者template<>void Swap<int>(int &,int &);

隐式方式:

template<class T>int add(T a,T b){T c=a+b;return c}

 

内联函数:

inline int add(int a,b){cout<<a+b;}它的执行过程是用内联代码替换函数调用

 

单独编译的原理

如果只修改了一个文件,则可以只重新编译改文件,连接器再更新与刚更改的文件的相关代码?而不用再从头来一遍

 

一个源文件就是一个翻译单元

 

register int a;  a是一个寄存器变量,直接存在cpu的寄存器中。

 

一个文件要引用另一个文件里已定义过的变量,可以用:    extern int a;

 

限定符volatile

   volatile:让cpu轮询内存的被volatile修饰的变量值,一但有改变量,则更新其变量值的缓存值(位于cpu的寄存器上)

mutable即使结构(或类)变量为const..某成员也可以被修改。

strut data{mutable int a}

const data test={1}

test.a++

 

接口

     对接交流的洞口。电脑的充电口,就是电脑暴露给外界的接口。电脑允许外界给自己充电。如果没有该接口,那么,外界将不能给电脑充电。事物提供给外界操纵自己的某些行为的口或者外界和事物进行对接交流的口叫做接口

 

面向对象和面向过程

面向过程,它是将数据表示和函数原型放在一个文件中,但是面向对象是将数据表示和函数原型放在一个类声明中。

 

this它是一个指针:它指向当前调用对象

就是this是指向对象的指针,它是对象的地址。要返回对象,可以这样做*this

 

栈其实也是一个容器。泛形类非常适用于通用编程。它是抽象数据类型的代名词ADT{abstract data type}

 

函数重载或函数多态

用户能够定义多个名称相同,但特征标(参数列表)不同的函数,这被称为函数重载或函数多态。它能让你能够用同名的函数来完成相同的基本动作即:使这种操作被用于不同的数据类型,它增加了编程的灵活性,使其语言不再那么笨拙。

 

a.h文件:

头文件  b.h  中引用了  a.h

头文件  c.h  中引用了  b.h和引用了a.h,这样a.h就在c.h中引用了两次。为避免这种情况。我们可以引变一下a.h

#ifndef A_H_

#define A_H_

class A{......}

 

 

重载运算符的例子:

class Time{

    private:

        int hour ,minute

   public:

        Time(int h,int m=0);

}

main(){

     Time lastTime(6,30)

     Time nowTime(8,30)

      Time total

     

      Time Time::operator+(const Time &t) const{

             Time sum

              sum.minute=minute+t.minute

             sum.hours=hour+t.hour+sum.minute/60

             sum.minute %=60

             return  sum

}

}

两种方式使用

total=lastTime+nowTime

或者

total=lastTime.operator+(nowTime)

重载运算符左边是调用对象,右边是参数对象

 

friend关键字(友元)

class Time{

    private:

        int hour ,minute

   friend void add()  

}

void add(){

   Time time

    int c=time.hour+time.minute

}

      

只有类声明可以决定哪一个函数是友元。

是类的友元函数,则该函数就能完全访问类的所有东西。friend关键字相当于类暴露了所有的接口给友元函数

 

动态内存分配:

是在程序运行时确实要使用多少内存,才分配多少内存。例如用户的输入。当用输入完后,根据输入的字符的多少决定分配多大内存存储用的输入,而非在编译时就分配10000大的字符数组来存储用户的输入

 

静态类成员:

类的所有对象共享同一个静态成员。无论创建多少对象。程序都只创建一个静态类变量{所有对象都可以共享他}

 

内存缓冲区:

它其实就是一个字符数组

定位new运算符,可以指定内存区域创建对象。如在内存缓冲区创建对象

Test *test1,*test2

Test(const String &s="default string"){cout<<s;}

char *buffer=new char[512]

test1=new (buffer)Test    ~对象也是在buffer数组中(字符串[对象]是存在内存缓冲区的)

test1=new (buffer)Test("i am in buffer")

test2=new Test("i am in heap")

字符串(对象)是存在堆里面的

正确的释放方式

test->~test();

delete[] buffer;

new在哪里:

这是声明,它并不会进行类加载(编译器先检查有无该类。但并不会把类加载进内存,这步只是链Test类的代码整合到一个exe文件里面(.class字节码文件里面。本质它还在磁盘上))

Test test;

声明和定义放一块(定义会进行类加载,声明只是起一个检查作用,未声明而直接定义编译器会不认识你定义的东西,就会报错)

 

定位new运算符能指定对象存在哪个位置

 

对于new出来的对象,位于堆的要delete(它会自动调对象的析构~这是隐式方式)

 

对于new出来的对象,位于内存缓冲区的(就是存在数组里的)除了delete[]数组名。还要显式调用对象的析构函数

 

class Person{

          virtual void say();

}

class Student :public Person{virtual void say(count<<"hello");}公有继承。java一般是公有继承

class Student :private std::string{}私有继承

class Student :protect std::String{}保护继承

class Student :public Person{},public Writer{}多继承

 

virtual关键字:相当于打上一词多义的标签{标志着该方法会根据上下文的不同呈现出不同的行为特征}~~~不加virtual..它永远就只有一个意思{一种形态或者说一种行为}

 

c++ primer plus(二)

编译器对非虚方法使用静态联编

编译器对虚方法使用动态联编

 

虚函数的实现原理:

    当编译器发现类中有虚函数时。就会在类实例化对象的时候为每个对象添加一个隐藏的成员变量{它是一个指针数组,或者叫地址数组。里面存放了每个虚函数的地址。它根据函数特征标量决定的,有标量不一样的虚函数,地址表中就新增一个该虚函数的地址}。当执行虚函数时,它不是直接通过函数地址调函数,而是先遍历对象的存了所有虚拟数的地址数组(表),找到对应的与上下文匹配的虚函数的地址,再执行[其实这无形增大了开销:1.增加了一个虚函数地址数组,2.查找虚函数表(就是遍历数组)又会花一点时间]

但付出的代价是值得的。总得来说:有没有虚函数表,是编译器干的事情

虚函数的是与不是

1.构造函数不能是虚函数:因为这没什么意义

2.析构函数应当是虚函数,除非类不能做基类{基类的析构函数应该要设成虚函数}

3.友元不能是虚函数{只有成员才能是虚函数}

4.派生类没有重新定义函数。将使用基类版本,派生类位于派生链,则更新虚函数版本

5.重新定义继承的方法,不是重载,也不是重写,但他会隐藏所有的同名基类方法???

6.函数的返回类型是基类引用,则可以改为指向派生类的引用{这叫返回类型协变}

class Person{

      virtual Person &bulid(int n)

}

 

class Student : Person{

      virtual Student &bulid(int n)

}

 

protect:派生类能访问基类的protect属性

private:其属性只能由基类自己访问

public:公开的属性

 

//包含纯虚函数的类只能作基类,不能有类对象。即:

抽象类不能有对象。因为抽象类的对象没有实际意义

 

纯虚函数和虚函数:

纯虚函数声明的结尾处为0

virtual double Area()=0;   ~~~~纯虚方法只能在子类中定义{或者叫实现},它是抽象类的标志{抽象类必须有一个纯虚方法}

virtual double Area();~~~~~虚方法

 

抽象类:

圆是特殊的椭圆,正方形是特殊的长方形。但二者不具备继承关系(继承了就会变得很笨拙~~圆如果继承了椭圆,可圆又不需要长半轴和知半轴。正方形继承长方形,正方形又不需要长宽。它有边就够了。)他们的关系应该是这样:

 

class Ellipse{x模坐标 ,y 纵坐标,a长半轴,b 短半轴,angle角度......virtual void rotate()绕中心旋转的一个方法..move()}

 

class circle{x,y,r半径....move()方法}

 

抽象类BaseEllipse{x,y,move()}

 

 

什么不能被继承

构造函数。析构函数。赋值运算符(赋值运算符的特征标随类而异。不同的类会对运算符进行不同的重载,因此继承它是没有意义的)是不能继承的。他们都是基类独有的东西

 

模板类,模板方法:

template<class T>可以理解为注解(用在类上,说明这是模板类,用在方法上,说明这是模板方法)

 

template<class T>

class Test{

      Test(T  t,int i):Student(t),Writer(i){//是一个构造方法,又是一个多继承,还同时调用了两个基类构造方法来构造<初始化>派生类}

      template<class U>

      U tf(U u,T t){}

      void  Show(){}

}

 

 

测试方法:

Test<int>test(3 ,5);     这里的int对T

test.show();

test.tf(6 ,9)          这里的6对应U

 

异常:

程序的异常信息是由编译器给出的。程序异常。则发生中断。中断消息由编译器给出。

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值