C++学习笔记

一、引用

1, 声明时必须初始化,类似于const的指针。

如int const &b=a; 此时a的值可以修改,但b的值不可修改,a修改后b 的值跟着一起修改,int 与 const的顺序可以改变,并且只有在初始化的时候才能给引用指定所引用的变量,除此之外不能修改所引用的变量,但不能引用一个数组。

2, 函数返回引用时其实是返回的原变量的别名,也可在函数的形参上引用,此时函数将可以修改实参的值,可以加上const加以 限制。

Const int &func( const int & param )

此函数的返回值带const,要求接收函数返回值的变量也要带const,如必须这样调用函数:int const &a=func(b);

应当避免返回当函数终止时不再存在的内存单元的引用,可以用new来解决(因为new申请的空间必须用delete来释放,函数终止时也会保留着临时申请的空间)。

返回指针的函数:const wendao const *ptr=test1(sys1);此时ptr=NULL或者ptr=0都是可以的,但是不能让ptr指向wendao以外的类型。对于 const wendao *test1()和wendao const * test1()测试的时候没发现区别,都是返回指针。但收接此函数返回值的变量也必须要const,如可以这样接收参数:const wendao *ptr=test1()或者wendao const *ptr=test1();

二、函数重载

Void print( const char *str , int width )

Void print( double d, int width )

若形参中含有const,则编译器将根据实参是否为const来决定使用哪个原型。

此外,内联函数(在定义和声明时都加上inline)也可以重载,所谓内联函数就是编译的时候直接把函数代码块替换成调用函数处的代码,跟宏类似,但比宏功能要强大。

三、函数模板

1, 当一个函数被重载的次数比较多且功能相似时,可以定义一个函数模板,如:

Template <class wendao> void swap( wendao &a, wendao &b ){

Wendao temp;

Temp=a;a=b;b=temp;

}

当a、b为int型时,编译器会自动生成一个swap( int &a, int &b)的函数,为char类型时也一样,此时称为模板的隐式实例化(就是模板生成函数时是隐性进行的)。

2, 模板的重载

Template <class wendao> void swap( wendao a[], wendao b[], int n ){函数体}

此模板重载了上面的模板,并且还添加了一个具体的类型 int,并非所有的模板参数都是模板参数类型。

3, 具体化和实例化

显式实例化:一般情况下模板生成函数的过程叫实例化,若无特殊指定则为隐式实例化,也可以显式实例化,强制模板生成相应的函数,如在函数声明时:

Template <> void swap<int>( int, int ) ;

显式实例化声明,在函数调用之前就生成了int型的函数了,否则的话还要在调用的时候生成,此类函数只需要在声明块内添加此此声明而已,不用对模板函数做任何处理。

显式具体化:有时候想对某个类型的模板函数做特殊处理,就要对这类型的函数特殊定义:

Template <> void swap<int>(int &a, int &n ){函数体}

Template <> void swap(int &a, int &n ){函数体}

这样就可以在模板函数里对int型的参数做特殊处理了,此类函数声明如下:

Template <> void swap<int>(int &a, int &n );

Template <> void swap(int &a, int &n );

优先级:普通函数跟显式具体化的函数的优化级一样,谁在前面定义就先调用谁 > 模板函数

四、new和delete操作符动态分配内存

1, 编译器使用3块独立的内存:一块用于静态变量、一块用于自动变量、一块用于动态存储变量,如new、malloc等申请的空间。

2, new与delete是成对出现的,对每个用new申请的空间只能delete一次,如果申请的时候是new [] 则释放的时候要delete [].

3, New操作符返回所申请空间的指针:type *ptr = new 数据类型(初值),

Int *ptr = new int(10); 或者 char *ptr = new char(‘a’);

4, 布局new操作符:

#include <new>

Char buffer[50];

Char *ptr = new (buffer) char[‘a’];

在buffer空间里申请一块内存用来存储字符a,就是限制new操作符申请空间的区域。

五、结构和类

1, 在C++中,类与结构体的唯一区别是:结构体默认的访问类型是public,而类是private,C++对结构体进行的扩展,使之有跟类一样的特性。

2, 位于类声明中的函数都将自动成为内联函数,也可以在声明中不定义函数,在类外定义函数时加上inline关健字也能成为内联函数。类的每个对象都有自己的存储空间,用于存储其内部变量和类成员,但同一个类的所有对象共享同一组类方法,即每种方法只有一个副本。

3, 构造函数和析构函数:如果未提供任何构造函数(仅当如此时),C++将提供一个默认的构造函数且不做任何事,如果定义了构造函数,将不会再提供任何默认的构造函数,只能自己去定义默认构造函数,并且每个类只能有一个默认构造函数,构造函数没有返回值。析构函数既没有参数也没有返回值。

Class stock

{

Private:

Int num;

Public:

Stock(); ////默认构造函数

Stock( const char *co, int n=0, double pt=0 ); ///构造函数

~stock(); ///析构函数

}

Stock food = stock( “World Cabbage”, 250, 44.56 ); ///显式的调用构造函数

Stock garment( “World Cabbage”, 250, 44.56 ); ///隐式的调用

Stock *ptr = new stock( “World Cabbage”, 250, 44.56 ); ///指针调用

默认构造函数初始化对象:stock wendao; //注:后面没有小括号

4, const成员函数:如果不想一个成员函数修改对象值的话,声明和定义函数的时候要如下: 

public: void show() const;

定义的时候: Void stoic::show() const {函数体} ///这样在show方法里只能读对象成员值。

5, 每个类对象都有一个this指针,指向当前的对象的地址,故*this返回当前的对象。

6, 对象数组:stock mystuff[4]; ///用默认构造函数创建有4个对象的数组

Stock mystuff[4] = {

Stock( “World Cabbage”, 250, 44.56 ),

Stock( “abcefdsaadddd”, 250, 44.56 ),

Stock( “wendao”, 250, 44.56 ),

}

在此数组中,只显式初始化了部分元素,故剩余的元素将用默认构造函数初始化。

7, 声明类时:Private: const int len=50;这样是不行的,因为声明类只是描述了对象的形式,并没有真正的创建对象,所以在对象创建之前并没有存储值的空间。但我们可以这样:private: enum{ len=50 }; char arr[len],除此之外类的成员变量不能在类声明时初始化。

8, 定义类的静态方法的时候,在类声明里加上static就可以了,定义的时候不用加了,并且在static方法里不能用this指针、只能访问static型的成员变量,但普通成员方法也可以访问static型变量,也可以用this指针指向static型变量。并且静态成员变 量必须初始化(如果没有访问他们则不必初始化),如在类声明之后 int test::num=90;(声明的时候static int num)。静态成员是单独存储的,不是对象的一部分,所以可以在类声明之外用单独的语句初始化。

在VC++6.0里,静态成员方法调用静态变量时可以这样:num或者test::num。

可以用类名::静态方法()、对象.静态方法() 调用静态方法。

9, 不可以出现类似于static int func() const这样的修饰符。

10, 操作符重载:operator 操作符( 参数列表 ) [ const 如果不想修改数据 ]

Ø 重载后的操作符必须至少有一个操作数是用户自定义的类型,防止用户用标准类型重载操作符。

Ø 使用操作符时不能违反操作符原来的语法规则。

Ø 不能定义新的操作符。

Ø 不能重载的操作符有:sizeof、.、.*、::、?:等。

11, 友元:在类声明区里用friend关健字声明方法,定义的时候不必加friend了,在此方法里可以访问对象的私有成员,权限跟类的方法成员一样,它不属于类。用友元重载操作符时,操作数的顺序和参数的顺序是一样的。

12, 类型转换函数:只接受一个参数的构造函数,可以将与该参数类型相同的值转换为对象,只有接受一个参数的的构造函数才能这样。如test wendao; wendao=7;这时就会调用test类中只有一个int参数的构造函数来生成类。也可以在此构造函数前加上explicit来关闭自动转换的特性,这时必须显式的进行转换。

Explicit test(int a); 这时再用wendao=7就不好使了,必须这样:test(7)或者(test)7;才反回一个对象。隐式转换主要是以下情况:

Ø 将test对象初始化为int型的值时。

Ø 将int值赋给test对象时。

Ø 将int型值传递给接受test对象作为参数的函数时。

Ø 返回值被声明为test的函数试图返回一个int型值时。

Ø 在上述任一种情况下,使用可转换为int类型的内置类型时。

13, 转换函数:将对象转换为其他类型的函数:operator 类型(); 不需要有返回值类型,因为后面的类型已经规定好返回值的类型了,并且也不需要有参数,必须是类的方法。

Public Operator int() [const]; ///当将对象转换为int型时会调用此函数,这是声明时的。

Test::operator int() [const] { 函数体 } ///这是定义时的

Test wendao; Int a= (int)wendao;//此时会调用上面的函数,将对象转换成了int型。

14, 复制构造函数:用于将一个对象复制到新创建的对象中,它用于初始化过程中,而不是常规的赋值过程中,它接受一个指向类对象的常量引用作为参数:

Class_name( const Class_name & ); ////如test( const test &a )

新建一个对象并将其初始化为同类的现有对象时,复制构造函数将被调用。每当程序生成了对象副本时,如当函数按值传递对象、函数返回对象时都将使用复制构造函数,有个test类的对象testOBJ:

Test testOBJ1(testOBJ);

Test testOBJ1 = testOBJ;

Test testOBJ1 = test(testOBJ);

Test testOBJ1 = new test(testOBJ);

默认的复制构造函数逐个的复制非静态成员的值,如果类成员本身也是某个类的对象则将使用这个类的复制构造函数来复制成员对象,也叫浅复制,利用复制构造函数可以完成深度复制。静态数据不受影响。

15, 赋值操作符:Class_name & Class_name::operator=( const Class_name & ),其实就是重载的=号操作符,它用于将一个已有的对象赋给另一个已有的对象时,初始化总是会调用复制构造函数,而使用=号操作符时也可能调用赋值构造函数。默认的赋值构造函数也是对成员逐个复制,如果成员本身为类对象,则会使用这个类的赋值构造函数来复制该成员,静态数据不受影响。

16, 类继承:公有继承、保护继承、私有继承:

Ø 实例化子类时,父类首先被实例化,即首先调用父类的构造函数,如果没有指明要调的父类的构造函数的话,将使用父类的默认构造函数,如下面的例子: subclass::subclass(param1, param2, param3,param4【总参数】):ParentClass( param2, param4【需要调用父类构造函数初始化的参数】){函数体},总之,派生类构造函数通过成员初始化列表将基类信息传递给基类构造函数,并且,派生类只初始化新增的数据成员,基类的数据成员还是由基类的构造函数初始化。派生类过期时,首先调用派生类的析构函数再调用基类的析构函数。

Ø 基类指针或者基类引用可以在不进行显式类型转换的情况下指向派生类对象,不过此时基类指针或者引用只能调用基类方法,除非该方法是虚函数。但不可以将派生类指针或者引用指向基类对象。

Class parentClass {

         virtual int show();

         int set();

};

class subclass:public parentClass{

         [virtual] int show();

         int set()

};

virtual关健字声明的时候加上,定义的时候不必加,从父类继承来的虚函数在子类里自动成为虚函数,此时子类里的virtual关健字是可选的。对于一个指针,parentClass *ptr = new subclass; ptr->set()将调用父类的方法,而ptr->show()将调用子类的方法,因为通过引用或者指针调用继承类里的方法时,对虚函数调用的是指针指向的对象的方法,而对普通函数则调的是指针类型本身所属对象的方法。如果在派生类里重新定义基类方法,最好将它定义为虚函数,也常常为基类声明一个虚拟析构函数。构造函数和友元不能是虚函数。

Ø 继承和动态内存分配:类的默认赋值操作符将自动使用基类的赋值操作符来对基类组件进行赋值。当派生类和基类都使用动态内存分配时,派生类的析构函数、复制构造函数、以及赋值操作符都必须使用相应的基类方法来处理基类元素,其中对于赋值操作符可以用作用域解析操作符来显式调用基类的赋值操作符来完成,如baseClass::operator=( obj );

Ø 虚函数工作原量:编译器会给每个对象添加一个隐藏成员,此隐藏成员中保存了一个指向函数地址数组的指针,这种数组称为虚函数表(vbtl),vbtl中存储了为类对象进行声明的虚函数的地址,如,基类对象包含一个指针,该指针指向基类中所有虚函数的地址表。派生类对象将包含一个指向独立地址表的指针。如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址;如果派生类没有重新定义虚函数,该vbtl将保存函数原始版本的地址。如果派生类定义了新的虚函数,则该函数的地址将被添加到vbtl中。无论类中包含的虚函数是多少个,每个对象只添加一个地址成员,只是表的大小不同而已。调用虚函数时,程序将查看存储在对象中的vbtl地址,然后转向相应的函数地址表。如果使用类声明中定义的第N个虚函数,则程序将使用数组中的第N个函数地址。因此,当使用虚函数时,每个对象都将增大,增大量为存储地址的空间。对每个类,编译器都创建一个虚函数列表(数组)。每个函数的调用都需要执行一步额外的操作,即到表中查找地址。

Ø 纯虚函数:

Class test{

Private:

Double x;

Double y;

Public:

Test(double a=0, double b=0):x(a), y(b) { }; ///另一种构造函数初始化方法。

Virtual double show() const =0; ///纯虚函数

}

当类声明中包含纯虚函数时,则不能创建该类的对象,纯虚函数的定义是可选的。从这样的类继承下来的子类必须实现该纯虚函数才可以实例化。【类似于php中的抽象类】

posted @ 2012-07-25 08:57  奇葩文刀  Views(201)  Comments(0)    收藏  举报