您的位置:首页 > 编程语言 > C语言/C++

C++学习笔记

2010-05-09 17:19 141 查看
C++中,类的大括号后面要加“ ; ”



尽量避免使用void main(),因为在某些系统中,main()函数返回的int值,将被调用该程序的系统接收,通过该返回值,调用该程序的系统可以判断程序是否运行正常

良好的便程习惯(命名规则)

(1)宏定义用大写字母加下划线表示,如MAX_LENGTH;

(2)函数命名,如setName(), getCountOfStudents();

(3)指针变量加前缀p,如*pNode ;

(4)BOOL 变量加前缀b,如bFlag ;

(5)int 变量加前缀i,如iWidth ;

(6)float 变量加前缀f,如fWidth ;

(7)double 变量加前缀d,如dWidth

(8)字符串变量加前缀str, 如strName;

(9)枚举变量加前缀e, 如eDrawMode;

(10)类的成员变量加前缀m, 如m_strName, m_iWidth ;

(11)对于int, float, double 型的变量,如果变量名的含义十分明显,则不加前缀,避免烦琐。如用于循环的int 型变量i, j, k 。



使用cout 和cin要包含的库

#include <iostream.h>

或者

#include <iostream>

using namespace std;





字符型数据作为字符使用时,是有符号还是无符号,一般无所谓。

字符型作为单字节整数使用时,最好明确表示为signed char 或unsigned char。



在c++中布尔类型:bool,可以和整型相互转化



变量初始化就是在定义变量的时候给变量一个初值

切记要给变量初始化,特别是局部变量



在定义变量是可以用const来修饰,表示变量的值不能修改。常变量(const常量)



常变量存储在数据区,可以对其取地址。cout << &B << endl;



在以后的编程中,我们尽量不用#define来定义符号常量,而用const来定义常量。



下面的定义方式不合法:

int n = 10;

int a
;

而下面的定义方式合法:

const int n = 10;

int a
;



C++语言允许在为二维数组初始化时省略行下标值,但列下标值不能省略。

int m[ ][3]={ 85, 87, 93, 88, 86, 90 };

int m[ ][3]={ 85, 87, 93, 88, 86 };

int m[ ][3]={ {85}, { 78, 91, 82} };



枚举类型中输出每个值时,输出的时数字,如:enum colors_type{red, green, blue}; cout << red << endl;输出值为0



char *sp = "string";

//sp的值是string,而*sp = s。用这种方法定义字符串,如果是char sp = “string”,报错



在for循环里面,++p和p++ 一样,如:for(int *p = a; p != e; p++)

引用: int a = 3; int &m = a; 引用是通过别名来访问某个变量

引用一旦被定义以后,对它的使用与对普通变量的使用是完全一样的。

指针和引用的区别:

指针是通过地址间接访问某个变量,

引用是通过别名直接访问某个变量。

引用必须初始化,而一旦被初始化后不得再作为其他变量的别名。



动态存储分配需要使用指针、new运算符和delete运算符

运算符new用来申请所需的内存

<指针变量> = new <类型>

<指针变量> = new <类型>(<初值>);

也可以为数组申请内存:

<指针变量> = new <类型>[<元素数>];

含义:

new运算符从堆中分配一块与<类型>相适应的存储空间,如果分配成功,则将其首地址存入指针变量中,否则指针的值为NULL

动态存储分配的存储区域通过指针访问



若要释放连续的空间,必须在delete后面跟上[]



动态存储分配应注意的问题

1、要确认分配成功后才能使用,否则可能造成严重后果。

int *ptr = new int(12);

if( ptr != NULL ) cout << *ptr;

2、动态分配的存储单元不会自动释放,因此不用的存储单元一定要及时释放,防止内存泄漏。

3、动态存储分配四步走:

使用new申请分配内存,并 用指针指向该内存

判断内存分配是否成功

使用

使用delete释放内存



在一个函数里面如果有两个for循环,如果一个里面定义了int i = 1,则在另一个里面只需要声明 I = 1 ,不需要再写int了,这是和Java不同的地方



静态数据成员



注意:

⑴静态数据成员必须初始化,其格式如下:

数据类型 类名::静态数据成员名 = 值;

⑵初始化在类外进行,而且在数据成员名的前面不加static;

⑶初始化时使用作用域运算符来标明它所属的类,如:初始化上述count静态数据成员的语句如下:

int Myclass::count = 9;

⑷访问静态数据成员的方法有如下几种:

方法1 :类名::静态成员名;

方法2:对象名. 静态成员名;

方法3:对象指针→静态成员名;



如果在函数定义时没有明确指定类型,则默认类型为int。



引用是给一个已经定义的变量重新起一个别名,而不是定义一个新的变量,定义的格式为:



例如:

int number = 10;

int &newnum = number ;

int number = 10,

int &newnum(number );

含义:

通过引用名与通过被引用的变量的变量名变访问变量效果一样。



注意:

声明一个引用时,必须同时初始化。

该引用名不能再作为其他变量的别名。

如果这个变量的引用值变了,那么这个被引用的变量的值也会变



在形参里面f( int *y, int &x ),y是一个指针,而x是一个引用,它随着函数的调用而改变,而指针不会,如:

#include<iostream.h>



int* f( int *y, int &x )

{

int a = 5;

int *s = &a;

y = s;

x = a;

return s;

}



int main() {

int a = 6, b = 7;

int *pa = &a;

int *x = f( pa, b );

cout << "*x=" << *x << endl;

cout << "*pa=" << *pa << endl;

cout <<"b=" << b << endl;

return 0;

}

变量的持续性、作用域、链接性

这里我想写一下变量的持续性、区域性、和链接性。先简单的介绍一下这三个属性是什么意思。所谓持续性就是说这个变量所持续的时间,即它在内存中存在的时间。区域性简单的说就是变量的作用域,就是说这个变量在哪个范围内有效的。链接性是说,这个变量是不是允许外部文件使用。先说自动变量。什么是自动变量呢?自动变量就是指在函数内部定义使用的变量。他只是允许在定义他的函数内部使用它。在函数外的其他任何地方都不能使用的变量。自动变量是局部变量,即它的区域性是在定义他的函数内部有效。当然这说明自动变量也没有链接性,因为它也不允许其他的文件访问他。由于自动变量在定义他的函数的外面的任何地方都是不可见的,所以允许我们在这个函数外的其他地方或者是其他的函数内部定义同名的变量,他们之间不会发生冲突的。因为他们都有自己的区域性,而且它没有链接性(即:不允许其他的文件访问他的)。来看看自动量的持续性。计算机在执行这个函数的时候,创建并为它分配内存,当函数执行完毕返回后,自动变量就会被销毁。这个过程是通过一个堆栈的机制来实现的。为自动变量分配内存就压栈,而函数返回时就退栈。静态变量:静态变量与自动变量的本质区别是,静态变量并不像自动变量那样使用堆栈机制来使用内存。而是为静态变量分配固定的内存,在程序运行的整个过程中,它都会被保持,而不会不销毁。这就是说静态变量的持续性是程序运行的整个周期。这有利于我们共享一些数据。如果静态变量在函数内部定义,则它的作用域就是在这个函数内部,仅在这个函数内部使用它才有效,但是它不同于自动变量的,自动变量离开函数后就会别销毁,而静态变量不会被销毁。他在函数的整个运行周期内都会存在。在函数外面定义的变量为全局变量,工程内的所有文件都可以访问他,但是它在整个工程内只能定义一次,不能有重复的定义,不然就会发生错误,而其他的文件要想使用这个变量,必须用extern来声明这个变量,这个声明叫做引用声明。这一点很重要,如过你没有用extern 来声明在其他文件中已经定义的全局变量,就来使用它,就会发生错误如果你只是想在定义他的文件中使用它,而不允许在其他的文件中使用它,那么就用关键字static来在函数外面声明变量。这样这个变量在其他文件中将不可见,即它的连接性而内部链接。有一点是我们只得注意的像:如果你在函数外这样声明一个变量,const int a ; 变量a的连接性为内部链接,只能在定义他的文件内使用。还有如果你在定义静态变量的时候并没有给变量初始化,则静态变量将被自动初始化为0;



内联函数



内联函数的定义必须出现在对该函数的调用之前,即内联函数必须在调用函数之前定义,即使在函数原形前加上inline关键字也不行。因为在进行扩展时必须事先知道替换的语句是什么。

函数体内含有循环、switch分支或复杂嵌套的if语句时,虽然能被定义为内联函数,但可能起不到内联函数的作用。

内联函数实际上是一种用空间换时间的方案,使用内联函数节省了程序运行的时间开销,但是增加了代码占用内存的空间开销。因此在决定是否要使用内联函数时应权衡时间开销与空间开销的矛盾。



带有默认参数值的函数



double func(double x,double y,int n=1000)

如果函数调用时为相应参数指定了参数值,则参数将使用该值;否则参数使用其默认值。



注意

默认参数的设置应从参数表的最右边开始设置,从右到左依次设置。

void func( int a, int b = 1, int c, int d =2);//error

如果一个函数有两个以上默认参数,则在调用时可以省略从后向前的连续若干个参数值。



如果函数原型中已给出了参数的默认值,则在函数定义中不得给出默认值,即使所指定的默认值完全相同也不行,否则编译出错。



用static关键字来保存执行信息,生存周期为整个程序的运行



全局变量:



声明于所有函数之外的变量称为全局变量,全局变量都是静态的,即具有和程序执行期相同的生存期。全局变量的作用域也可以扩充到其他源程序中。



函数在编译的时候,都会被编译器分配一个入口地址,指向这个地址的指针就是函数指针



int(*f_p)(const char*); // 定义一个函数的指针

int *func_p(char a ){…}// 定义一个返回指针的函数



可以用typedef简化函数指针的使用,如下例:

typedef int(*FUN)(int a, int b);

//声明(*FUN)是一个函数指针类型

FUN func_p;

//func_p为一个返回整型和两个整型参数的函数指针



回调函数:

回调函数就是指通过函数指针调用的函数。当你把函数指针作为参数传递给另一个函数,另一个函数通过该指针调用该函数,我们就称被调用的这个函数为一个回调函数。



析构函数的作用与构造函数正好相反,它是用

来完成对象被删除前的清理工作的。通常,析构

函数是在对象的生存期即将结束时,由系统自动

调用的。随后这个对象就消失了,对象所占的内

存空间也被释放了。



访问声明:



当一个类B是从其父类A私有派生的时候,它所继承的父

类公有段或保护段的所有成员,在这个派生类中的访问属性

都成为私有的,如果从类B再派生出类C,则类C不能直接访

问其间接基类A的任何非静态成员。为了使程序员具有必要的

灵活性 ,以方便应用系统的开发,C++语言提供了一种调节

机制,称为访问声明,它是对私有派生方法的一种补充。



对于函数,只需要写函数名,如:Base::f;



虚基类:(对应java里面的抽象类)



为解决在多重继承环境中因公共基类而带来的二

义性问题,C++语言提供了虚基类(虚拟基类)机制。

为了把一个基类定义为虚基类,必须在定义派生

类时在父类表中虚基类名字前加上关键字Virtual。声

明虚基类的一般格式为:

class 派生类名:Virtual 访问权修饰符 父类名{ ... };



必须每个派生类都初始化虚基类?

由于在派生类DD的实例中只有基类Base中定

义的数据成员a的一份拷贝,因此,使用虚基类不

仅能消除公共基类可能带来的二义性问题,而且

能节省内存空间。当然,如果在解决实际应用问

题时,在派生类对象中确实需要公共基类中定义

的数据成员的多份拷贝,则不宜使用虚基类。此

外,虚基类增加了系统的时间开销,使用虚基类

机制时应该考虑这个因素。非必要时不要过多地

使用虚基类。



在使用虚基类机制时应该注意以下几点:

⒈必须在最新派生出来的派生类的初始串列中,调用虚基类的构造函

数,以初始化在虚基类中定义的数据成员。

⒉初始串列中各个基类构造函数的调用顺序是,先调用虚基类构造函

数,然后调用非虚基类构造函数。例如,在本节给出的示例程序中,

在执行DD类的构造函数时,先调用虚基类Base的构造函数,然后再调

用普通基类Derived1和Derived2的构造函数。

⒊虚基类的构造函数仅调用一次,虚基类的构造函数由最新派生出来

的派生类负责调用。

⒋如果在最新派生出来的派生类的初始串列中,没有显式调用虚基类

构造函数,则编译程序将调用虚基类的缺省构造函数。



基类与派生类之间的转换



实际上是用派生类对象从其基类继承来的数据成员的值,给基类对象的相应的数据成员赋值,也就是说能够自动转化的前提条件是:派生类从其基类共有派生

如:

#include<iostream.h>

class A {



int x;

int y;

public:

A(int a, int b) {

x = a;

y = b;

}

int Gety() {

return y;

}

int Getx() {

return x;

}

};

class B : public A {

int z;

public:

B(int a, int b, int c) : A(a, b) {

z = c;

}

int Getz() {

return z;

}

};

void main(int argc, char* argv[])

{

A a(8,9);

cout << a.Getx() << endl;

cout << a.Gety() << endl;

B b(1,2,3);

cout << b.Getx() << endl; //通过继承来的公有成员函数获取基类中私有成员

cout << b.Gety() << endl;

cout << b.Getz() << endl;

a = b; //将派生类对象赋值给基类

cout << a.Getx() << endl;

cout << a.Gety() << endl;

//cout << a.Get_z() << endl;

//b = a; //编译出现C2679错误,不能将基类赋值给派生类

}

当程序执行到“b = a;”时,由于基类对象中并不包含派生类中的新增成员,赋值后会造成派生类对象的某些数据成员或方法无意义。



指针对象必须用箭头



Const:

可以使用关键字const来说明const对象,这样说明的对

象的任何数据成员都不能被修改。

程序员必须在定义类时提供附加信息,告诉编译程序哪些

成员函数不会修改数据成员。不修改数据的函数称为只读函数。

const对象只调用只读成员函数,保证了任何数据成员都不

被修改。C++语言规定,把关键字const写在成员函数的参数表

后面,就可以把该成员函数声明为只读函数。

如: int GetMonth() const{ return month; }



友元:

友元机制是对封装机制的补充,利用这种机制,一个类可以赋予某些函数(普通的外部函数)访问这个类私有成员的特权。能够访问一个类的私有部分而又不是该类成员函数的函数,称为该类的友元函数。声明了一个类的友元函数,就可以用这个函数直接访问该类的私有数据,从而减少了开销。但是,这样做并不是使数据成为公有的或全局的,未经授权的其他函数仍然不直接访问这些私有数据。因此,使用一个友元函数并没有彻底丧失安全性,慎重、合理地使用友元机制并不会使软件可维护性大幅度降低。

注意:

(1)“class Matrix;”是一个空声明,它由关键字“class”后跟类名

“Matrix”组成。使用空声明语句表明,类“Matrix”需要被向前引用,它的定义在后面。

(2)友元函数除了具有访问指定类的私有成员的特权之外,其他方面与普通函数完全一样。

(3)虽然需要在类定义体中声明友元函数,但是,友元函数并不是该类的成员



⑷ 调用友元函数时必须在它的实在参数表中给出要访问的对象。

⑸ 一个类的成员函数也可以作为另一个类的友元函数。

⑹ 如果需要把一个类的所有成员函数都作为另一个类的友元,则可以把这个类声明为另一个类的友元。

⑺ 既可以在类的私有段声明友元,也可以在类的

公有段声明友元,这两种做法语义相同。

⑻ 友元不具有传递性。

⑼ 友元不具有交换性。

⑽ 应该慎用友元。 



继承:



共有继承:基类的公有、保护成员在派生类中保持原来的状态,而私有成员不能被派生类的对象所访问

私有继承:基类的公有、保护成员在派生类中作为私有成员,而私有成员不能被派生类的对象所访问

保护继承:基类的公有、保护成员在派生类中作为保护成员,私有成员不能被派生类的对象访问



私有成员在派生类中是无法直接访问的,只能通过调用基类的公有成员函数方式实现



不涉及派生时,保护成员也私有成员的地位是完全一致的,不能被直接访问



指向基类的指针也可以指向派生类,但通过这个基类类型的指针,只能访问从基类继承的成员



派生类的对象可以赋给基类对象,反之不成立,因为派生类的所占的空间不会比基类小,当派生类的对象赋给基类对象时,只是初始化的基类有点成员



程序在创建一个派生类对象是,系统首先自动创建一个基类对象



如果派生类的基类也是一个派生类,则每个派生类只需要负责其直接基类的构造,依次上溯



析构函数先执行派生类的



抽象类

构造函数可以是protected的,这时它虽不可被外部访问,但是可以被派生类成员访问。如果一个类的所以构造函数都是protected的,那么这个类就不能生成对象。但其派生类可以创建对象,这样的类称为抽象类



虚基类



虚基类不是一种新的类型的类,而是一种派生方式。采用虚基类方式定义派生类,在创建派生类的对象时,类层次结构中虚拟类的成员只出现一次,即基类的一个副本被所有派生类对象所共享

模板:(工厂)



是将一个数据类型参数化的工具,它把“一般性的算法”和其“对数据类型的实现”区分开来

模板提高了软件的重用性。当函数参数化或数据成员可以是多种类型而函数或类所实现的功能有相同时,使用模板在很大程度上简化了编程



函数模板:



一种不指定某些参数化的数据类型的函数,在函数模板被调用时根据实际参数化的类型决定这些函数模板参数的类型,一个函数模板可以用来生成多个功能相同但参数和返回值不同的函数



在调用模板函数是即创建函数模板的一个实例,这个过程称为函数模板的实例化。函数模板的实例化有编译器完成:编译时韩式模板本身并不产生可执行代码,只有在韩式模板被实例化是,编译器才按照实参的数据类型进行类型参数的替代,生成新的函数



函数模板只能用于定义非才成员函数



#include <iostream.h>

template< class T >

T max( T *array, int n )

{

T max = array[0];

for( int i = 1; i < n; ++i )

{

if( array[i] > max )

{

max = array[i];

}

}

return max;

}

int main()

{

int a[] = { 1, 4, 5, 8, 6 };

int ma = max<int>( a, 5 );

double b[] = { 1.1, 4.4, 5.5, 8.8 };

double mb = max<double>( b, 4 );

return 0;

}

类模板

类模板是对类的更高层次上的抽象。类模板称为呆参数化的类,也称为类工厂。



与函数模板不同,类模板不是通过调用函数是实参的数据类型来确定类型参数具体所代表的类型,二十通过在使用模板类声明对象时所给出的时间数据类型确定类型参数



类模板的实例化是由编译器编译时完成



#include<iostream.h>

template<class T>

class Compare {

public:

Compare(T a, T b) {

x = a;

y = b;

}

T max() {

return x > y ? x : y;

}

T min() {

return x < y ? x : y;

}

private:

T x, y;

};

int main() {



Compare<int> cmp1(3, 7);//实例化

cout << cmp1.max() << endl;

cout << cmp1.min() << endl;

Compare<double> cmp2(23.33, 64.3);

cout << cmp2.max() << endl;

cout << cmp2.min() << endl;

Compare<char> cmp3('a', 'A');

cout << cmp3.max() << endl;

cout << cmp3.min() << endl;

return 0;

}



//含有多个参数的类模板定义

#include "iostream.h"

template <class T1, class T2> // 使用2个类型参数

class MyTemClass // 定义模板类

{

private:

T1 x;

T2 y;

public:

MyTemClass(T1 a, T2 b) { x=a; y=b; };

void ShowMax()

{ cout<<"MaxMember="<<(x>=y?x:y)<<endl; };

};

int main()

{

int a=100;

float b=123.45F;

MyTemClass< int, float > mt(a, b);

// 声明模板类的对象,类模板的实例化

mt.ShowMax();

return 0;

}



#include<iostream.h>

template<class T, int n>

class Stack {

private:

int m_nTop;

const int m_nMaxElem;

T m_stack
;

public:

Stack();

~Stack();

bool Push(T elem);//压栈函数声明

bool Pop(T &elem);//出栈函数声明

};

template<class T, int n>

Stack<T, n>::Stack():m_nMaxElem(n),m_nTop(0) {}//初始化

template<class T, int n>

Stack<T, n>::~Stack(){}

//压栈

template<class T, int n>

bool Stack<T, n>::Push(T elem) {

if(m_nTop < m_nMaxElem) {//如果最上面的标号小于最大数

cout << " 压入 :" << elem << endl;

m_stack[m_nTop] = elem;

++ m_nTop;

return true;

}

else

return false;

}

//出栈

template<class T, int n>

bool Stack<T, n>::Pop(T &elem) {//这里传的是引用

if(m_nTop > 0) {

cout << m_stack[m_nTop-1] << " 出栈" << endl;

--m_nTop;

if(m_nTop > 0)

elem = m_stack[m_nTop-1];//将第二个作为栈顶值

return true;

}

else

return false;

}

int main() {

Stack<int, 10> s;

s.Push(2);

s.Push(4);

s.Push(8);

int v1;

s.Pop(v1);

s.Pop(v1);

Stack<double, 20> d;

d.Push(11.23);

d.Push(32.3443);

d.Push(565.2);

double v2;

d.Pop(v2);

d.Pop(v2);

d.Pop(v2);



return 0;

}

总结:可以用来传递数组元素个数的方法有如下3种:

(一)用两个实参,一个是数组名,一个是指出它的长度

(二)使用对数组的引用,此时它的数组元素是作为实参传入函数的

(三)使用vector向量来代替数组



数组排序:



#include<iostream.h>

template<class T>

void f(T a[], int n) {

T temp;

for(int i = 0; i < n; i++) {

for(int j = i + 1; j < n; j++) {

if(a[i] < a[j]) {

temp = a[i];

a[i] = a[j];

a[j] = temp;

}

}

}

for (i = 0; i < n; i ++) {

cout << a[i] << " ";

}

cout << endl;

}

int main() {

int a[5] = {9,2,6,8,4};

f<int>(a, 5);

double b[4] = {5.3, 3.7, 8.5, 1.0};

f<double>(b, 4);

char c[5] = {'d', 'e', 'a', 'w', 'e'};

f<char>(c, 5);

return 0;

}

C++中允许基类对象的指针指向其派生类的对象



虚函数:

利用虚函数可以在基类和派生类中使用相同的函数名和参数类型,定义不同

的操作。这样,就为同一个类体系中所有派生类的同一类行为(其实现方法

可以不同)提供一个统一的的接口



虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态成员函数,更不能是构造函数。因为虚函数仅适用于有继承关系的类对象。



调用虚函数的操作只能是对象指针或对象引用,否则仍为静态绑定



多态性:通过继承相关的不同的类,他们的对象能够对同一函数调用做出不同的响应



当撤销派生类的对象是,先调用派生类析构函数,然后自动调用基类析构函数,如此看来析构函数没必要定义为虚函数,但是,假如使用基类指针指向派生类对象,而这个派生类对象是用new运算创建的,当程序使用delete运算撤销派生类对象时,这时只调用了基类的析构函数,而没有调用派生类的析构函数



虚函数的存取有点特别,它的存取要看首先定义她的类中该函数是共有还是私有,如果在基类中是公有成员,则无论在派生类中该虚函数是共有还是私有都被看作共有成员存取,同样如果虚函数在基类中私有成员,则在派生类中被看作私有成员存取



虚析构函数



为了防止内存泄漏,需要将析构函数定义为虚函数,这样无论指针所指的对象是基类对象还是派生类对象,程序执行时都会调用相应的析构函数,一般我们都要将所有基类的析构函数都定义为虚析构



采用虚函析构后,c++自动采用动态联编方式选择析构函数,这使用于所有的虚函数



如果基类的析构函数定义为虚析构函数,则派生类的析构函数就会自动成为虚析构函数



带有虚函数的类比没有的多出一个执行虚函数入口地址的指针,在调用类的构造函数是,指向基类的指针自动转换乘this指针,通过this指针可以正确的得到虚函数入口地址表中的相应的指针,从而实现了多态性



纯虚函数、抽象类



格式:virtual 函数类型 函数名(参数表) = 0 ;



纯虚函数就是没有实现部分的函数,不能产生对象,=0本质上是指向虚函数入口地址的指针定义为NULL



C++使用纯虚函数的目的是建立一个类的同用框架,用于引导建立一系列机构类似的完整派生类



抽象类:至少有一个成员函数是纯虚函数或定义了protected属性的构造函数或析构函数 ?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: