NOTE:2015-01-04

来源:互联网 发布:网络恐吓怎么取证 编辑:程序博客网 时间:2024/05/21 11:02
//***********************************************  2015-01-04  *************************************************

虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态重载。
凡是含有纯虚函数的类叫做抽象类。这种类不能声明对象,只是作为基类为派生类服务。

针就是地址,指针是一个表示地址的表达式。
指针既可以是常量,也可以是变量。代码段:
int x = -1;        // 假设&x == 45
int* ptr;        // 定义指针变量ptr
ptr = &x;        // ptr的值为x的地址45
定义指针变量的语法,定义了指针变量ptr。语句
int* ptr;
将ptr定义为int*类型的变量,将int*读作整型指针,相应的,ptr能保存int类型数据的地址,如此处的x的地址。
如果定义了一个类C,则C*则表示C型指针,即由它定义的指针变量能保存C类对象的地址。
简言之,声明一个整型指针的变量,它就可以存放整型数据的地址

“int*”表示int型指针;“&x”表示x的地址,&为地址运算符;
“int&”是引用调用:为了将参数的引用传递给被调函数,要在参数的数据类型后增加符号“&”,例
void f( int& a,int& b );

创建类的两种方法:
  • 使用关键字“class”:所有成员默认为private的;
  • 使用关键字“struct”:所有成员默认为public的。

关于类中的public、private和protected,明确:
  • public成员在任何地方都能使用,无论是类的内部还是外部;
  • 基类中的private成员只能在基类中使用,即使在子类中也不能访问基类中的private成员;
  • protected成员在继承层次结构中可以直接使用,但在类外部(如main函数)不能使用,因为是受保护的;
  • 子类可以访问从基类中继承的protected,但不能访问基类对象的受保护的成员,也就是说,一个对象属于基类,但它不属于子类;
  • private成员可以继承,但不能在子类中使用,除了友元函数外,只有类中的方法才能访问本类中的private成员;
  • 受保护的成员不仅可以继承,也能在子类中使用。此外,基类中受保护的成员,在子类中也是受保护的。
总结下来,在使用范围上:
  • private:只在本类中
  • protected:在整个继承结构中
  • public:在整个类的内部和外部

继承中的构造函数:创建子类的对象时,要调用基类的构造函数,基类的构造函数用于初始化“从基类继承”的部分成员和完成其他功能。而子类中有自己的构造函数的,这个构造函数就用于处理子类中添加的属性。

如果基类中有构造函数,但没有默认构造函数,则子类中的构造函数必须指定调用基类中的哪个构造函数。
而如果基类有一个默认构造函数,子类中也有构造函数,而且子类中的构造函数没有指定调用基类中的哪个构造函数,这种情况下创建子类对象时,自动调用基类的默认构造函数。

假设D是B的子类,则关于构造函数的相关规则总结如下:
  • 如果D有构造函数,但B中没有构造函数,创建D的对象时就会自动执行D的构造函数;
  • 如果D中没有构造函数,但B中有构造函数,B中必须有默认构造函数,创建D的对象时自动执行B的默认构造函数;
  • 如果D中有构造函数,B中有默认构造函数,只要D的构造函数的头部不指定调用B中其他的构造函数,创建D的对象时则自动调用B的默认构造函数;
  • 如果D和B中都有构造函数,但B中没有默认构造函数,D构造函数的头部必须指定调用B的哪个构造函数,创建D的对象时要执行B中指定的构造函数。

protected继承方式
  • 在基类中的公有成员,在子类中也是受保护的;
  • 在基类中的受保护的成员,在子类中也是受保护的;
  • 在基类中的私有成员,在子类中不能使用。
例:
class B        // 基类
{
...
};
class D:protected B        // 子类,受保护的继承方式
{
...
};
中D是基类B的子类,继承方式是受保护的。

私有继承方式
  • 基类的公有成员,在子类中是私有的;
  • 基类的受保护成员,在子类中是私有的;
  • 基类的私有成员,在子类中是无法访问的。

在继承中,默认的继承方式是私有继承。如:
class D:B        // 子类,默认为私有继承方式,省略了关键字private
{
...
};

多重继承:一个子类有多个基类。例如:
class D:public B1,public B2
{
...
};

//-----------------------------------------------------------------------------------------------------------------
关于继承的总结
  • 在继承过程中,默认的继承方式为私有继承方式,所以如果要想按公有方式继承,就必须使用public关键字指定;
  • 除了友元函数外,不允许在类的继承层次结构外访问受保护的成员;
  • 除了通过友元函数外,不允许在类的外部访问私有成员;
  • 如果基类中有构造函数,但无默认构造函数,子类的构造函数必须在头部指定调用基类的哪个构造函数(指定语法如:D( int a ) : B( a ) { ... });
  • 如果子类中有一个方法的方法名与基类中某个方法的方法名相同,则子类中的方法会隐藏基类的方法。
//-----------------------------------------------------------------------------------------------------------------

函数代码在内存中的起始地址称为函数的入口指针。例如,main函数的入口地址是指,main函数的可执行代码在内存中的起始地址。因此,多态实际上指运行时将函数名和入口指针进行绑定

C++多态性的三个要求:
  • 必须有继承的层次结构;
  • 在层次结构的类中,都有一个具有相同函数特征的虚方法(使用关键字virtual声明虚方法);
  • 必须有一个指向基类的指针或基类的引用,使用这个指针或引用调用虚方法。

如果基类中声明的虚方法,在子类中没有显示地指定同名的方法为虚方法,则系统自动将子类中同名方法定义为虚方法。
如果在类的声明(亦即类的接口)外部定义虚方法,只需要声明方法时使用virtual关键字,而定义时不需要virtual关键字。

各种进制常数的表示方法:
  • 十进制(0~9):不能以0开头,如27;
  • 八进制(0~7):必须以0开头,如033;
  • 十六进制(0~9,a,b,c,d,e,f):必须以0x0X开头,其中字母a~f可以为大写,如0x1B。

//***********************************************  2015-01-05  *************************************************

变量就相当于一个容器,只是这个容器是根据所装东西的需要来制造的,也就是说,什么类型的容器只能对应装什么类型的东西。因此,声明一个int型变量,它就能存放int数据,声明一个指针变量(指针就是地址嘛),它就能存放地址。我们可以到放了对应东西的容器去找对应的东西,反过来讲,放了对应东西的容器引导我们去找对应的东西。所以,存放了地址的指针变量(亦即赋了值)就相当于装了对应东西的容器,它可以引导计算机去找对应的东西(地址),而对应的地址“所在地”放了一台“机器”,这台“机器”就是函数(类的方法),所以就可以用存放了地址的指针来调取这台机器,这便是通过指针调用方法。过程代码演示如下:
// 首先,制造一个容器p,这个容器只能装TradesPerson类型的指针(也就是指向TradesPerson类的地址)
TradesPerson* p;
// 指针容器做好了,那么装什么指针都可以,所以接下来,给这个容器装对应的东西,也就是给这个指针赋值
p == new Tinker;
// 至此,容器p装了一个东西,这个东西就是new即时创建的Tinker类的对象的地址。这个Tinker类的对象就相当于刚盖好的一个房子,房子的地址已经装在容器p里面了。而房子里有台机器,这个机器就是一个函数(亦即方法),现在想用一下这台机器,那么就得先根据这个地址找到房子,找到房子自然就能用这台机器了。这“按图索骥”的过程便是通过指针p调用方法
p -> sayHi();

delete运算符释放创建动态对象时申请的内存空间。例如释放对象p所占用内存空间的语法:
delete p;

只要创建了指向基类的指针,就可以用new动态创建基类以及其衍生的子类的对象,并将返回值(所创建对象的地址)赋予之前所创建的指向基类的指针。

构造函数不能使用关键字virtual,而析构函数可以使用关键字virtual。

我们说一个函数具有多态性,是指它在运行时进行绑定,在C++中,只有虚方法在运行时绑定,因此,只有虚方法实现了真正的多态。

对于基类和子类中具有多态性的方法,必须是具有相同特征的虚方法。(相同特征指:函数类型相同、函数名相同、函数的参数相同)

一个类只有满足以下条件才能成为抽象基类:类中有一个纯虚方法。
只要一个类中有一个纯虚方法,这个类就是一个抽象基类。

纯虚方法:是指在声明时初始化为0的方法。例如:
virtual void open() = 0;
,指的是值为0;,指的是virtual。

将一个变量初始化为0时,0就保存在变量中。而将一个虚方法初始化为0时,只是标明这个方法是纯虚方法,包含这个方法的基类称为抽象基类。

抽象基类的子类必须重写基类中所有的纯虚方法,否则,子类本身是一个抽象类,不能将子类实例化为对象。
抽象基类主要用于采用虚方法,其所有的子类如果要实例化为对象,必须重写基类中的所有方法。

只有虚方法才能是纯方法(亦即只有virtual的函数才能赋值为0),非虚方法和顶级函数声明过程中不能将其初始化为0。

调用一个void函数其实就是,当程序run到这个调用处时,就跳转到那个void函数执行那个函数,完了再跳回来(如果后续还有语句需要执行的话),继续执行调用处后面的语句。
而调用一个非void函数就是,当程序run到这个调用处时,跳转到那个非void函数执行那个函数,执行完后会有个返回值,这个返回值被返回到调用处,参与调用处的程序执行。

重载是指相同的名称或符号具有不同的含义。
函数重载是指一个函数有多种不同的定义。
运算符重载是指C++中各种运算符有多种不同的定义。用户定义一个函数,便于用户按现有运算符的语法形式实现不同的功能。重载运算符既可以是一种方法,也可以是一个顶级函数。
简言之,重载其实就是重新定义

一般地,使用一个方法重载双目运算符时,这个方法中只有一个参数,如在类C中重载运算符“+”,以便将两个C的对象相加,结果存在另一个C的对象中,可以在C中声明一个名为“operator+”的方法:
class C
{
public:
          C operator+( C& );        // 参数为引用调用
...
};
如果使用方法重载单目运算符,方法中没有参数,如:
 C operator!();  

重载运算符既可以是一种方法(即类中定义的函数),也可以是一个顶级函数。
使用顶级函数重载运算符,例如,重载“+”,使用两个C的对象作为参数,其结果也为C的对象,“operator+”顶级函数的定义为:
C operator+( C& c1, C& c2 )
{
...
}
可以使用下面语句调用重载函数:
a = operator+( b, c );
因为使用了关键字operator,所有通常采用下面方式调用:
a = b + c;

通过重载不能改变运算符的优先级和语法。例如,无论是内置运算符还是重载运算符,双目运算符“||”有两个参数,即使重载后“||”的优先级也与原来的一样。

new、new[]、delete、delete[]是内存管理运算符。

以下运算符必须使用类中的方法进行重载,而不能使用顶级函数进行重载:
[]:下标运算符
=:赋值运算符
():函数调用运算符
->:间接访问运算符

引用调用在参数的数据类型后增加符号“&”会改变从主调函数传递来的实际参数的值。因为当主调函数调用被调函数时,被调函数是直接将主调函数中的参数拿来操作的,而不是先把主调函数的参数复制到自己这里,然后拿这个复制品来操作。所以,如果需要被调函数改变实际参数的值,就可以采用引用调用。

引用就是某个目标变量的“别名”(alias),对引用的操作与对变量直接操作效果完全相同。

//-----------------------------------------------------------------------------------------------------------------
关于引用:
  • 传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作;
  • 使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好;
  • 使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
//-----------------------------------------------------------------------------------------------------------------

友元函数:在类C中声明f时使用关键字friend,可以使f成为类C的友元函数。

//***********************************************  2015-01-06  *************************************************

重载自增(++)自减(--)运算符:
声明
operator++();
中没有参数,重载了前置自增运算符。声明
operator++( int );
中有一个整型参数,重载了后置自增运算符(自减运算符的重载与此类似)。其中整型参数仅仅是为了与前置自增运算符重载进行区别。

从程序员的观点来看,现代计算机内存系统分为三个部分:
  • 程序区:用于保存函数体的代码以及外部变量静态变量
  • 栈:用于保存参数非静态局部变量
  • 堆:用于保存动态分配的数据单元。堆也称为自由存储区。

要使用堆中的存储单元,程序员要使用new或new[]运算符;要收回堆中的存储单元,程序员要使用delete或delete[]运算符。

C++中的变量有四种存储类别:auto(自动变量)、static(静态变量)、extern(外部变量)、register(寄存器变量)
变量的存储类别决定了它的作用范围(即在哪些位置可以访问该变量)和生命期(即存储单元中的数据可以存在多长时间)。

//-----------------------------------------------------------------------------------------------------------------
  • auto
在代码块中定义的变量默认的存储类别为auto。一个auto变量必须定义在代码块中。如果显示地使用auto关键字,则要将auto关键字放在数据类型的左边。
作用范围:代码块内。
  • static
在C++中,有两种情况会用关键字static:一是在类的内部,此时static指定的是类而不是对象成员;二是类的外部。
对于类外部的静态变量,无论静态变量定义在代码块内还是代码块外,静态变量存储在程序区,静态变量的生命期是程序的整个生命期。
作用范围:如果静态变量定义在块内,则其作用范围为块内;如果静态变量定义在所有块的外面,其作用范围为从定义的位置开始,直到定义变量的文件结束为止。
定义在类外面的静态变量有两个常用的用法:
  1. 可以用静态变量保存多次调用一个函数之间的值,如记录调用某个函数的次数;
  2. 可以用静态变量作为文件中的全局变量。
  • extern
定义在所有代码外部的变量的存储类别默认为外部变量。外部变量必须定义在所有块外部。
外部变量存储在程序区。外部变量的生命周期就是程序的生命周期。
除外部变量外,其他变量的声明和定义是一致的。而对于外部变量,其定义和声明有很大区别,谨记以下两点:
  1. 外部变量只能定义一次,且必须定义在所有代码块的外部,定义时通常省略关键字extern;
  2. 外部变量能够声明多次,可以在代码块内部或外部声明,声明时必须使用关键字extern。
如果定义外部变量时使用了extern关键字,程序员必须提供一个初始值,以便区分是声明变量还是定义变量,如:
extern int x;        // 声明
int main()
{
...
}
extern int x=-999;        // 定义
在声明中初始化外部变量是非法的(如在main函数中声明外部变量“extern in x=-999;”将出错)
作用范围:从定义的位置开始,到定义变量的文件结束为止。与定义在代码块外的静态变量作用范围一样。
在某个文件中定义外部变量后,在其他文件中声明这个外部变量,声明变量的文件也能访问这个变量了。
建议:声明时使用关键字extern,定义时不使用extern。

从作用范围、生命期和存储区域三方面对存储类别进行总结如下:
 存储类别 最大作用范围 生命期 存储区域auto  代码块代码块 堆栈  static文件 程序 程序区  extern 程序程序 程序区 
//-----------------------------------------------------------------------------------------------------------------

运算符new用于申请一个数据存储单元,而new[]用于申请多个数据存储单元。这两个运算符可以用于分配类的对象的存储单元。
调用new[]时,[]用于指定申请的数据单元的个数。

//***********************************************  2015-01-07  *************************************************

C++中没有字符串类型,而是采用字符数组来表示字符串。用字符数组表示字符串时,最后一个元素以空字符“‘\0’”作为结束标志(注意,“‘\0’”表示一个字符)。例如,表示字符串“Avin”:
'A' 'v' 'i' 'n' '\0'  [0][1] [2] [3] [4] []是下标运算符。

//-----------------------------------------------------------------------------------------------------------------
关于数组
  • 下标运算符用于指定数组中的某个元素,通过某个元素与第一个元素的偏移量指定这个元素,方括号里的整型数字即表示偏移量。所以,一般数组的第i个元素下标为i-1。
  • 上溢:超出下标合法范围的错误称为上溢。数组下标合法范围:0,1,...,size-1,其中size表示数组长度。
  • 下溢:下标表达式是一个负数的错误称为数组下溢。
  • 用于初始化数组的值要用{}括起来,如:int nums[ 3 ] = { 5, 2, 1 };
  • 如果{}中用于初始化数组的数的个数少于数组长度,如:int nums[ 3 ] = { 5 };,那么,有几个数,就初始化数组的前几个元素,其他元素则自动初始化为0。此例为:
 5 0 0 [0][1] [2] 所以,如果要将数组中所有元素都初始化为0,可以只提供一个0作为初始化的值,编译器会将其余元素全部初始化为0。
  • 如果定义数组的同时,初始化数组中所有元素的值,则可以省略数组的长度。如:int nums[] = { 5, 2, 1 };
  • 如果定义数组时不对其进行初始化,则必须给出具体的数组的长度。
  • 如果在所有语句块之外定义数组,则编译器会自动将所有的存储单元初始化为0,程序员可以不对其进行初始化。
//-----------------------------------------------------------------------------------------------------------------

可以用sizeof( ... )运算符计算“...”所占存储单元(亦即字节)的个数。

相量:是指堆中分配的数组,亦即,相量是动态分配的存储单元的集合。例如:
int* pInt = new int[ 10000 ];
动态申请了一个存储单元相量,该相量包含10000个整型存储单元。
释放存储单元相量的语法为:
delete[] 相量名;

//-----------------------------------------------------------------------------------------------------------------
反向引用运算符“*”对指针进行运算,以引用指针指向的数据单元的值。
所谓“反向引用”,是在“内容”和“地址”之间互相来回。例如:
int main()
{
    int x = 1;        // x的内容
    int* ptr;        // “ptr”是个整型指针,表示地址,因此“ptr”的内容为一个地址
    ptr = &x;        // x加上&后变成地址,即“&x”表示地址,将这个地址赋予ptr,ptr就表示x的地址
    *ptr = -999;         // ptr加上反向引用运算符*后变成内容,因为ptr表示x的地址,所以反向对应的内容即为x的内容,因此该赋值语句将-999赋给x,x的内容即由原来的1变成现在的-999
    ...
}
如下表:
 表达式 值 描述 x 1 x的内容 &x 32 x的地址
 ptr 32 ptr的内容,等于x的地址 *ptr 1 ptr的反向引用,等于x的内容关于诸如多次反向引用,如**ptr,详见《C++应用程序设计》p433。
//-----------------------------------------------------------------------------------------------------------------

类型转换:
static_cast<...>(...)
其中,static_cast为关键字,尖括号<>内为要转换成的类型,圆括号()内为要进行类型转换的表达式。

指针数据类型void*:其所定义的指针为通用指针,将任何类型的指针转换成void指针时,都不需要做显式的转换。如:
char c;        // 字符型变量
void* p;        // 通用指针
p = &c;        // 不需要类型转换,直接赋值
实际上,p是一个通用指针,可以将任意地址表达式赋给它,不管地址是什么类型的。

创建一个模板类如Array,声明开始为:
template
class Array
{
    ...
};
也可以写为:
template  class Array
{
    ...
};
其中T是一个参数,它接在关键字class或typename后面,并用尖括号括起来;class和typename含义相同。

//***********************************************  2015-01-08  *************************************************


0 0
原创粉丝点击