第二课:C++纵览

来源:互联网 发布:js获取img的alt 编辑:程序博客网 时间:2024/05/05 00:19

1.C++内置类型:int ,float,double,char,boolean等

   标准库类型:vector,string,list.complex number等

   介于上述二者之间的复合类型(compound type):数组和指针

2.数组不能直接赋值:比如 int a[10],int b[10]. a=b.是错误的。

3.静态内存分配和动态分配。静态分配:即编译器在处理程序的源代码时(程序执行之前)分配。动态分配:即程序执行时调用运行时分配。两种分配方法的主要区别是效率与灵活性之间的平衡准则不同。因为静态分配是在程序执行之前进行的,因而效率比较高。但是它缺少灵活性。他要求在成寻执行之前就固定知道所需的内存类型和数量。一般来说,储存位置数目的元素需要动态分配的灵活性。

  静态分配如:int ival=1024.

这条语句指示编译器分配足够的存储区以粗放一个整型值,该存储区与名字ival相关联。然后,用数值1024初始化该存储区。这些工作都是在程序执行之前就完成的。

有两个值与对象ival相关联,一个是它包含的值1024,另一个是存放这个值的存储区的地址。在C++中,这两个值都是可以访问的。

4.C++如何访问存储区的地址呢?简单点说,用指针来存放地址,用&来取地址。其中*叫做解引用操作符,而&叫做取地址操作符。

5。在C++中,指针的主要用处是用来管理和操纵动态分配内存。

     静态内存和动态内存分配的两个主要区别是:

     1.静态对象是有名字的变量,我们直接对其进行操作。而动态对象是没有名字的变量,我们通过指针间接地对它进行操作。

      2.静态对象的分配与释放由编译器自动处理。程序员只需要理解这一点,但不需要做任何事情。相反,而动态对象的分配与释        放,必须由程序员显式的管理,相对来说比较容易出错,它通过new和delete两个表达式来完成。


6.对象的动态分配可以通过new表达式的两个版本之一来完成。第一个版本用于分配特定类型的单个对象。如:

              int*pint=new int(1024);

    该条语句分配了一个没有名字的int类型的对象,对象的初始值是1024.然后,表达式返回对象在内存中的地址。接着,这个地址被     用来初始化指针对象pint.对于动态内存的分配,唯一的访问方式是通过指针间接的访问。

    new表达式的第二个版本,被用来分配特定类型和维数的数组。例如:int*pia=new int[4];

   分配了一个含有四个整数元素的数组,不幸的是,我们无法给动态分配的数组的每个元素显式的指定一个初始值。

   分配动态数组时,返回值只是一个指针,与分配单一动态对象的返回类型相同。例如pint和pia的不同在于,pia拥有四元元素数组的第一个元素的地址,而pint只是简单地包含单一对象的地址。当用完了动态分配的对象或对象数组时,我们必须显式地释放这些内存。我们可以通过delete表达式的两个版本之一来完成这件事情,而释放之后的内存则可以被程序重新使用。单一对象的delete表达式的形式如下:delete pint;

数组形式的delete表达式如下:delete【】pia;

如果忘了删除动态分配的内存,程序就会在结束时出现内存泄露(memory leak)的问题。内存泄露是指一块动态分配的内存,我们不再拥有指向这块内存的指针,因此我们没有办法将它返还给程序供以后重新使用。

7。每个类对象在被程序最后一次使用以后,将自动调用析构函数,一般的,析构函数会释放类对象使用和构造过程中所获取的资源。


8.接口和实现

一般的,我们称C++声明的”函数原型“为接口,它只是提供了参数,返回值,以及此操作过程的名称等信息,而函数的具体的操作方法(具体的代码)我们称之为实现。就好比,一个插头,或者一个按钮,你只要知道它的功能是干嘛(输电),这就是接口,而它到底是如何实现输电的呢?这就是实现。

比如:

class Test{public:     void test();//此处的成员函数void test()仅仅告诉告诉用户调用它时传递几个参数,以及它返回什么值,什么作用等,并没有告诉用户test()函数到底怎么完成这些功能的,这就是接口;};void Test::test(){...}//这里是实现,此处是test()函数具体的代码,用户无需关心它是怎么写的。
又如:Test abc;//此处定义了一个Test类型的对象abc,abc 一般我们称之为类的“实例”(不是实现)。一个没有定义对象的类,只是一个“概念”,而不是一个实体,即实际存在的东西。类的作用只有在定义了类的实例(也就是类对象)后才会体现出来(静态函数和成员例外)。(就好比人只是一个概念,而要具体到某个人,才是个实例)。仍以int i;为例,int就好比是类(class),而i好比是类的实例。
9.对于一个非虚拟函数的调用编译器在编译时刻选择被调用的函数,而虚拟函数调用的决定则要等到运行时刻。在执行程序内部的每个调用点上系统根据被调用对象的实际基类或派生类的类型来决定选择哪一个虚拟函数实例。

10.对于保护protected 访问级别的使用已经有了一些争论.有人认为使用保护访问级别允许派生类直接访问基类的成员,这破坏了封装的概念.因此所有的基类实现细节都应该是私有的private .另外一些人认为如果派生类不能直接访问基类的成员,那么派生类的实现将无法有足够的效率供用户使用。如果没有关键字protected 类的设计者将被迫把基类成员设置为public 你怎样认为?

 protected的确违反了封装原理,因为base class内的信息并未对derived class隐藏——虽然它对其他类是隐藏的。这样做主要是为了效率上的考量。在某些时候,子类直接访问基类的成员会提高效率。同时我们又不希望base class的data members是public。这样就完全违背了封装原理。另一方面,我们也不希望派生类通过get()和set()等public函数来访问基类的private data members。所以在高纯度的封装和实际效率之间,protected访问级别提供了一个良好的折衷方案。


11.另一个具有争议的话题是:要不要将某个member function声明为virtual。某些人批评说,万一class设计者没有辨识出某个函数应该成为虚拟函数。那么derived class(派生类)设计者就没有办法改写(override)这个函数了。所以,他们建议将所有的memberfunction都声明为虚拟函数。持相反意见的一方则认为,虚拟函数比非虚拟函数缺乏效率。由于它们没有办法做成inline函数(要知道,“做成inline函数”的行为发生在编译期间,而虚拟函数是在执行时期才获得解析),因此,可能成为程序缺乏效率的罪魁祸首------特别是对那些小小的,常被调用的“与类型无关的”函数(例如,IntArray中的size函数)。你认为呢?


解答:很有可能class设计者会预料(或者期待)某些函数将来会有不同的行为,并于derived class中被改写(overrideen).。如果base class设计者无法认识到这样的需要,derived classs设计者就将像划一艘无桨之船一样,因为他没有虚拟函数可改写。这是虚拟函数的必要性。但是一旦class拥有了一个虚拟函数,该class的每一个对象都将拥有一个虚拟指针(vptr)指向一个虚拟表格(vtbl),其中内含该class的所有虚拟函数地址(每个对象有一个vptr,每一个class有一个vtbl).因此,让所有的member function都成为虚拟函数,可能会影响效率,因为所有的member function都需要付出动态分派的成本。另一个重点是,虚拟函数无法inline。所以,基于效率因素,只有某些函数必须是virtual时,我们才将它声明为virtual,其结果就是它可以再derived classes中被改写(overriden).


12.本章的最后一个问题是C++标准库中的一个类型,响亮vector。向量有以下几种定义方法:

首先要#include<vector>

1.创建一个空的vector:vector<int>veco;

2.const int size=8; const int value=1024;int ia[4]={0,1,1,2};

   则:vector<int>vec1(size)就是size为8的向量,且它的每个元素都被初始化为0

  则:vector<int>vec2(size,value);是size为8的向量,每个元素都被初始化为1024

  则:vector<int>vec3(ia,ia+4是size为4,被初始化为ia的4个值

  则:vector<int>vec4(vec2);表示vec4是vec2的拷贝。

13.向量支持下标运算符【】。

14.向量还支持迭代器,vector<int>::iterator iter=vec.begin().iter是一个int向量类型的指针,指向vec向量的第一个元素。



原创粉丝点击