C++学习总结

一些需要知道的基础知识点:

在程序代码中是通过变量名对内存单元进行存取操作的,但是代码经过编译后将变量名转换为该变量在内存中的存放地址,对变量值的存取都是通过地址进行的。比如i+j的运算,如果i等于3,j等于4,程序是先根据变量名与地址的对应关系,找到变量i的地址,从第一个地址开始顺序读取四个字节数据放到CPU寄存器中;再找到j的地址,依次读取四个放到寄存器中,然后通过CPU的加法中断计算出结果。

在低级的汇编语言中都是直接通过地址来访问内存单元的,而在高级语言中才使用变量名访问内存单元,C语言作为高级语言却提供了通过地址来访问内存单元的方法,C++也继承了这一特性。地址可以形象地称为指针,意思是通过指针能找到内存单元,因为原本是通过地址找到内存单元的,所以一个变量的地址称为该变量的指针。如果有一个变量专门存放另一个变量的地址,它就是指针变量。在C++语言中,专门用来存放内存单元地址的变量类型,称为指针类型。

指针是一种数据类型,通常所说的指针就是指针变量,是专门用来存放地址的变量。而变量的指针说的就是变量在内存中的地址。变量地址在编写代码时无法获得,只有在程序运行时才可以得到。

指针跟常规的变量为赋值相同,没有具体指向的指针不会导致编译出错,但是可能会导致难以预料的错误,所以一旦定义指针,一定要让它有一个具体的指向,也就是说要有一个地址赋给它。

另外,定义指针变量的时候,跟其他常规变量为了区分,要加*号,但其实真正的指针变量是没有*号的,在使用的时候要注意。

指针进行运算其实就是地址进行运算,指针的加减运算是跟指针的类型有关的,比如int类型的指针加1,地址值并不是加1而是加4,因为int类型占四个字节。

※指针还可以指向空类型void。空类型指针可以接受任何类型的数据,例如void *p = NULL

这里有点问题,NULL代替空指针存在二义性,所以后来用nullptr来代替空指针。总之,只要记住nullptr代表空指针就可以了,而NULL在C++中都把它理解为0就可以了。

※const int *p = &i  表示这个指针是指向常量的指针,只能用来“读”内存数据,无法通过*p的方式更改内存的内容,即更改变量的值,但是可以改变自身的地址值,就是指向别的内存地址;

※int* const p = &i  表示这个指针是一个常量指针,什么意思呢,以为指针变量里存放的是地址,定义为常量指针的话,说明这个指针变量存放的地址值是不可以改变的,但是呢,可以通过*p的方式改变内存的数据;

※const int* const p = &i  表示这个指针是指向常量的常量指针,有点拗口,跟上面的对比,反正就是只能用来“读”内存数据,也无法通过*p的方式更改内存的内容,也无法改变自身的地址值。

 

指针和一维数组

一维数组和二维数组在内存中的存放结构都是线性的。数组第一元素的地址就是整个数组的存储首地址,该地址存放在数组名中,看吧,其实数组名就相当于一个指针变量了,里头存放的是数组的首地址。访问数组元素的方式有下标法和指针法。用数组名[i]这个方式可以访问数组内容,其实也可以通过*(a+i)的方式,当然我们也可以再单独定义一个指针变量,将数组的首地址存放到这个指针变量中,当然了,可以用&a[0]也可以直接用a即数组名,因为我们说过了数组名本来就存放的是数组的首地址。

*(p++)相当于a[i++],先对p进行*运算,再使p自增。

指针与二维数组

a[0]是二维数组第一个元素的地址,可以赋值给一个指针变量。

a代表二维数组的地址,通过指针运算符可以获取数组中的元素。

a+n表示第n行的首地址;

&a[0][0]既可以看做数组0行0列的首地址,还可以看做是二维数组的首地址;

&a[0]是第0行的首地址;

a[0]+n是第0行第n个元素的地址;

*(*(a+n)+m)表示第n行第m列元素;

*(a[n]+m)表示第n行第m列元素。

数组指针和指针数组

这个还没有理解好,尤其是数组指针,指针数组还好理解,就是存储指针变量的数组。

指针和字符数组

字符数组就是一个字符串,通过字符指针可以指向一个字符串,然后通过地址的加减实现读取。

传递地址

之前接触的函数都是实参传递进函数体后,生成的是实参的副本,函数体内改变副本的值并不会影响到实参,但是如果传递进去的是指针,即使是副本指针,指向的地址还是一样的,因此可以改变指针指向地址的内容。

指向函数的指针

指针变量也可以指向一个函数。一个函数在编译时被分配给了一个入口地址,这个地址可以理解为存放在函数名中,可以定义函数指针,指向这个函数,通过指针来调用函数。

比如:

int sum(int x, int y)

int *a(int, int);

a = sum;

调用的时候这样:

int c,d;

(*a)(c,d);

还可以定义指针函数,返回值是指针,也就是地址了。

空指针调用函数

空类型指针指向任意类型函数或者将任意类型的函数指针赋值给空类型指针都是合法的。使用空指针调用自身所指向的函数仍然按照强制转换的形式使用。

指针数组

 

构造数据类型

结构体

C++ struct结构体变量其实跟数组有点像,只不过数组是相同类型元素的集合,而结构体变量可以是不同类型数据的集合。

定义结构体变量有两种方式:1、在定义结构体的时候定义;2、定义完之后定义。

struct PersonInfo
{
    int index;
    char name[30];
    short age;
}XiaoMing;

或者
struct PersonInfo
{
    int index;
    char name[30];
    short age;
}
PersonInfo XiaoMing;

引用方式:1.使用成员运算符.来引用;2.定义结构体指针变量之后,使用指向运算符->来引用

 

结构体还可以进行嵌套,结构体的大小一般情况下都是结构体内成员的大小之和。

使用typedef可以给一个复杂的数据类型定义一个别名,比如int(*)(int i)很复杂,就可以用一个别名来代替。

 

枚举类型enum的应用

枚举类型就是用大括号将不同标识符放到一起,变量的值只能取自括号内的值,在定义时,编译器默认将标识符自动赋上整形常数。还可以自行修改整形常数的值,说白了就是一个标识符对应一个常数,用于一些判断或者什么,用起来方便一些。赋值的时候,不能直接赋整型数,但是可以通过强制类型转换来赋值。枚举类型的变量,实质其实是整型的数字,所以可以进行比较和运算。

结构体

结构体变量还可以做函数的参数,还可以使用结构体指针,使用结构体指针减少了时间和空间上的开销,能够提高程序的运行效率。

结构体还可以创建结构体数组,跟创建结构体变量一样,在定义结构体时创建或者定义完了之后用结构体名这种数据类型创建。可以定义结构体指针,指向数组,使用指向符号->进行读取,但是要注意,指针加1,指向的就是数组里的下一个结构体了。

共用体

共用体union数据类型其实和结构体很像,声明共用体数据类型变量和声明共用体变量一样,都有三种方式,前两种应该知道,第三种是省略了共用体数据类型变量名,在定义共用体之后接着声明变量。

要注意共用体跟结构体最大的区别在于内存方面。共用体变量所占的内存长度等于最长的成员的长度,一个共用体变量不能同时存放多个成员的值,某一时刻只能存放其实一个成员的值,这就是最后赋予他的值。

共用体的特点是:1.使用共用体变量的目的是希望用同一个内存段存放几种不同类型的数据,但是某个瞬间只能存放一种,而不是同时存放几种;2.能够被访问的是最后一个被赋值的变量,对新的赋值后原来的就失去作用了;3.共用体变量的地址和它的各成员的地址都是同一地址。4.不能对共用体变量名赋值,不能引用变量名来得到哦一个值,不能在定义共用体变量时对它初始化,不能作为函数参数。

自定义数据类型

跟前面的数据类型重命名一样的,使用typedef标识符进行类型的重命名,或者说是自定义数据类型,其实是为了写起来方便或者用特定的单词代表特殊的意思。

宏定义

使用#define 可以对程序中经常出现的参数进行宏定义,这样在之后写代码的时候就可以用宏定义替换,一定程度上减轻了写代码的复杂度,程序在编译的时候自动进行替换,比如PI,一般标识符习惯用大写字母表示,以和变量名进行区分,要注意宏定义不是C语言,不需要在后面加分号,还可以定义数组和运算,定义运算的时候,括号建议都加上,以防止出现错误。如果字符串长于一行,可以在该行末尾用反斜杠\来续行。一般来讲,定义只有,作用域即有效范围指的是定义命令之后到此源文件结束,但也可通过#undef命令终止宏定义的作用域。

类的三大特性之一就是具有封装性,封装在类里面的数据可以设置成对外可见或者不可见,通过三个关键词可以设置类中数据是否对外可见。

public:这个属性的成员对外可见,对外也可见

private:对外不可见,只对内可见。派生的子类也不能访问这个区域。

protected:对外不可见,对内可见。派生的子类可以访问这区域。

有了不同区域,开发人员可以根据需求来进行封装。

一般的类成员,都是通过对象来访问的,不能通过类名直接访问。

如果将类成员定义为静态类成员,则允许使用类名直接访问。在定义静态类数据成员时,通常需要在类体外部对静态数据进行初始化。对于静态成员来说,不仅可以通过对象访问,还可以通过类名访问。

在一个类中,静态数据成员是被所有的类对象所共享的,不管有多少个类对象,静态数据成员只有一个,并且一个将其修改后,别的也将改变。

在类中,不允许在该类中定义所属类的对象,静态数据成员允许定义类的所属类对象,允许定义类的所属类型的指针类型对象。

类的普通数据成员不能作为默认参数。

类的静态成员函数只能访问类的静态数据成员,而不能访问普通的数据成员。静态成员函数不能使用const关键字

 对于类的非静态成员,每一个对象都有自己的一份拷贝,即每个对象都有自己的数据成员,不过成员函数却是每个对象共享的。那么调用共享的成员函数是如何找到自己的数据成员的呢,答案是通过类中隐藏的this指针。

实际上,每个成员函数中都隐含包含一个this指针作为函数参数,并在函数调用时将对象自身的地址隐含作为实际参数传递;编译器为了实现this指针,在成员函数自动添加了this指针对数据成员的方法。

void OutPutPages(CBook* this)
{
    cout<<this->m_Pages<<endl;
}

调用的时候,传递对象的地址到成员函数中,这就使得this指针合法了,当然这个地址在实际的调用时候可以隐藏不写。

友元类,友元方法这块儿不常用,暂时不仔细研究。

使用命名空间是消除命名冲突的最佳方式。

继承与派生

继承是面向对象的主要特征之一,它使得一个类可以从现有类中派生,而不必重新定义一个新类。继承的实质就是用已有的数据类型创建新的数据类型,并保留已有数据类型的特点,以旧类为基础创建新类,新类包含了旧类的数据成员和成员函数,并且可以在新类中添加新的数据成员和成员函数。

继承方式有三种public   private   protected三种继承方式

公有型派生表示对于基类中的public数据成员和成员函数,在派生类中仍然是public,对于基类中的private,派生类中依然是private

私有型派生表示对于基类中的public protected数据成员和成员函数,在派生类中可以访问,而基类中的私有,派生类不能访问

保护型派生表示对于基类中的公有、保护数据成员和成员函数,在派生类中均为保护,protected类型在派生类的定义时可以访问,用派生类声明的对象不可以访问,即在类体外不可以访问。

构造函数访问顺序:从父类派生一个子类并声明一个子类的对象时,先调用父类的构造函数,然后调用当前类的构造函数来创建对象;释放时候夏宁反。当父类的构造方法带有参数时,子类只能通过显式调用来调用父类的构造方法。

如果子类中定义了一个和父类一样的成员函数,那么一个子类对象调用的是子类中的成员函数,叫做子类隐藏父类的成员函数。

运算符其实是函数,所以运算符的重载其实就是函数的重载。

C++还可以进行强制类型转换,double(i)  或者(double)i

与别的面向对象的语言不同,C++允许多继承。

派生类调用成员函数时,先在自身的作用域内寻找,如果找不到,会在基类中寻找,但当派生类继承的基类中有同名成员时,编译器会出现错误,不知道具体调用哪个,一次需要在函数前指定类名。

多重继承中的基类构造函数被调用的顺序以类派生表中声明的顺序为准。

多态

多态是面向对象程序设计的一个重要特性。在C++中,多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数,发出同样的消息被不同类型的对象接收时,导致完全不同的行为。这里所说的消息主要指类的成员函数的调用,而不同的行为是指不同的实现。多态性通过联编实现,联编是指一个计算机程序自身彼此关联的过程,按照联编所进行的阶段不同,可分为两种不同的联编方法:静态联编和动态联编。在C++中,按照联编的时刻不同,存在两种类型多态性,即函数重载和虚函数。

 

发表评论

相关文章