C++基础知识
来源:互联网 发布:单片机控制3.3v 编辑:程序博客网 时间:2024/06/01 10:30
1 基本知识
1.1 运算符
(1)new最基本的两种用法:
① 开辟单变量地址空间
Ø int *p= new int ; //p指向一个int 型空间
Ø int *p = new int(5) ; //作用同上,但是同时将整数赋值为5
Ø Student *p=new Student; //Student 为类名,且有默认构造函数
Ø Student *p=new Student(‘a’); //Student为类名,利用构造函数初始化对象
② 开辟数组空间
Ø 一维: int *a = new int[100];开辟一个大小为100的整型数组空间
Ø 二维: int *a = new int[5][6]
Ø 三维及其以上依此类推.
注:new运算符为数组分配空间时,不能为数组进行初始化。
一般用法: new 类型 [初值]
(2)delete用法:
程序中使用new运算符在堆中开辟一个空间,当数据使用完后,一定要利用delete运算符释放在堆中开辟的空间,否则会出现内存泄漏。Ø int *a = new int;
delete a; //释放单个int的空间
Ø int *a = new int[5];
delete []a; //释放int数组空间
在程序中使用new运算符分配空间时,不要轻易修改指针指向的地址。因为当前指针指向了堆空间的地址,如果将其再指向其他地址,则导致无法释放堆空间,造成内存泄漏。
注:1)可利用CRT调试功能检测内存泄漏。
2)内存泄漏指用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元,直到程序结束。内存泄漏形象的比喻是“操作系统可提供给所有进程的存储空间正在被某个进程榨干”,最终结果是程序运行时间越长,占用存储空间(虚拟内存)越来越多,最终用尽全部存储空间,整个系统崩溃。由程序申请的一块内存,如果没有任何一个指针指向它,那么这块内存就泄漏了。
1.2 回调函数
回调函数一般用于截获消息、获取系统信息或处理异步事件。回调函数类似于一个中断处理函数,应用程序将回调函数的地址指针告诉,在符合设定的条件时,DLL会自动调用该函数。回调函数的特点如下:
Ø 回调函数是由开发者按照一定的原型进行定义的函数。
Ø 回调函数并不是由开发者直接调用执行。
Ø 回调函数通常作为参数传递给系统API,由该API来调用。
Ø 回调函数可能被系统API调用一次、也可能被循环调用多次。
为了使用回调函数,需要做以下工作:1)声明函数原型;2)定义回调函数;3)设置触发条件,即在函数中将回调函数名称转化为一个作为参数的地址,以便于DLL调用。
使用回调函数的示例程序代码如下:
…//此处代码省略
typedefint(*CallBack)(char *p); //声明CallBack类型的函数指针
int A(char *p)
{
AfxMessageBox(“A”);
AfxMessageBox(p);
return 0;
}
int B(CallBacklpCall,char *p)
{
AfxMessageBox("B");
AfxMessageBox(p);
lpCall(p); //借助回调完成的功能,即A函数来处理的
return 0;
}
void hello::OnButton9()
{
char *p="hello!";
B(A,p);
}
…//此处代码省略
1.3 数组与指针
(1)指针数组
(2)数组指针
数组指针,即指向一维数组的指针变量。其定义格式如下:
<类型> (*<指针变量名>)[<数组列数>];
其中,(*<指针变量名>)定义了指向一维数组的指针变量,该指针变量可指向二维数组中的某行。例如:
inta[3][3]={{1,2,3},{4,5,6},{7,8,9}};
int(*p)[3];
p=&a[0];
这样,则可用(*p)[0]、(*p)[1]、…(*p)[n-1]来表示数组a第i行的元素a[i][0]、a[i][1]、…a[i][n-1]。
当然,也可以用*(*p+0)、*(*p+1)、…*(*p+n-1)来表示数组a第i行的元素a[i][0]、a[i][1]、…a[i][n-1]。
注:*p两侧的括号不可缺少,如果写成*p[3],由于方括号[]运算级别高,因此p先与[3]结合,表示数组;然后再与前面的*结合,*p[3]是指针数组
(3)指针与二维数组
编译程序在为二维数组分配连续内存空间时,是按二维数组的行来逐行存放数组元素的。可以把二维数组的每一行看成是一个元素。例如:
int a[4][3];
可以看成是数组a包含4个元素,分别是a[0]、a[1]、a[2]、a[3],这四个元素是一维数组,因此这4个元素又分别表示一维元素的起始地址,即指针。
数组名用来代表数组的起始地址。对于二维数组来说,数组名a代表了第0行第0列的元素地址,也就是数组第0行的首地址,即a=& a[0][0]=&a[0]。a[i]就可表示第i行第0列元素的起始地址。一维数组的第i个元素的地址可表示为:数组名+i。因此一维数组a[i]中第j个元素a[i][j]的地址(&a[i][j])可表示为:a[i]+j。它的值可表示为*( a[i]+j)。
为了区分元素地址与行地址,C++中规定:
Ø 第一行的起始地址(行地址)用a+i或&a[i]表示;
Ø 第i行第0列元素的起始地址(元素地址):用a[i]或*(a+i)表示。
因此,第i+j行的起始地址就可表示为:a+i+j,&a[i]+j。第i行第j列元素的地址就可表示为:*(a+i)+j、a[i]+j、&a[i][j]、&a[i]+j。第i行第j列元素的值在前面加上取内容元素符*即可。如下表所示为二维数组的行地址与元素地址的各种表示方法及含义。
表.用数组名表示数组元素的行地址、元素地址和元素的值
表示方法
含义
a
二维数组名,数组的起始地址,数组第0行的地址
a[0]
第0行第0列元素的起始地址
a+0
第0行的起始地址
*a、*(a+0)
第0行第0列元素的地址
**a、**(a+0)、*a[0]、*(*(a+0)+0)
元素a[0][0]
a+i、&a[i]
第i行的起始地址
a+i+j、&a[i]+j
第i+j行的起始地址
a[i]、*(a+i)、&a[i][0]
第i行第0列元素的地址
*(a+i)+j、a[i]+j、&a[i][j]、*&a[i]+j
第i行第j列元素的地址
*(*(a+i)+j)、a[i][j]、*(a[i]+j)、*(&a[i][j])、*(*&a[i]+j)
第i行第j元素的值
示例代码如下:
#include<iostream.h>
int main()
{
inta[4][3]={{00,01,02},{10,11,12},{20,21,22},{30,31,32}};
int (*p)[3]=a; //定义指向一维数组的指针变量
for(intx=0;x<4;x++)
{
cout<<"a["<<x<<"][2]:"<<'\t'<<*(*p+1)<<endl;//显示第二列
p++;
}
cout<<"a:"<<'\t'<<'\t'<<a<<endl;
cout<<"a[0]:"<<'\t'<<'\t'<<a[0]<<endl;
cout<<"&a[0]:"<<'\t'<<'\t'<<&a[0]<<endl;
cout<<"&a[0][0]:"<<'\t'<<&a[0][0]<<endl; // a,a[0],均指向第0行第0列元素
return 0;
}
2 封装
2.1 构造函数和析构函数
(1)复制构造函数
当需要创建多个相同的对象时,可以采用复制构造函数,同默认构造函数一样,编译器提供默认的复制构造函数。示例代码如下:
#include<iostream.h>
class Pen
{
public:
Pen(int size);
Pen(Pen &pen); //声明一个复制构造函数
int GetSize();
void Write();
private:
int m_size;
};
Pen::Pen(int size)
{
m_size=size;
}
Pen::Pen(Pen &pen) //复制构造函数的定义
{
m_size=pen.GetSize();
cout<<"sizeand long";
}
int Pen::GetSize()
{
returnm_size;
}
void Pen::Write()
{
cout<<"thevalue of m_size is "<<GetSize()<<endl;
}
int main()
{
Penpen1(10);
pen1.Write();
Penpen2(pen1); //调用复制构造函数
pen2.Write();
return0;
}
(2)析构函数没有参数,所以析构函数是不能重载的。一个类可以有很多个构造函数,但是只能有一个析构函数。如果在撤销对象时,不需要做任何工作,可以不定义析构函数。
(3)构造函数和析构函数的调用顺序
通常情况下,创建对象时调用构造函数,在撤销对象时调用析构函数。最先被调用的构造函数,其对应的析构函数最后被调用;最后被调用的构造函数,其对应的析构函数最先被调用。
注:exit()函数和abort()函数的功能是终止程序的执行。当执行exit()函数时,系统要做终止程序前的收尾工作,即调用析构函数;而执行abort()函数时,直接终止程序的执行。因此,在构造函数中要终止程序的执行,不能使用exit(),这样会形成无限递归,但是可以使用abort()函数。
2.2 友元函数
友元不是成员函数,但是它可以访问类中的私有成员。友元的作用在于提高程序的运行效率。但是,它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。友元函数的形参一般是对象的引用。
利用友元函数访问类中的私有成员变量的示例代码如下:
#include<iostream.h>
class TestFriend
{
public:
void set(int i,int j);
friend voidget(TestFriend &tf); //友元函数的声明
private:
int x,y;
};
void TestFriend::set(int i,int j)
{
x=i;
y=j;
}
void get(TestFriend &tf) //友元函数的定义
{
cout<<tf.x<<" "<<tf.y<<endl;
}
void main()
{
TestFriend tf;
tf.set(1,2);
get(tf);
}
3 继承
(1)派生类的构造函数
构造函数不能被继承,因此派生类的构造函数除了对自己的数据成员进行初始化外,还必须通过调用基类的构造函数来初始化派生类的基类成员。如果派生的构造函数只在类的主体中声明,而在类的主体外定义,那么在声明时就不包括基类构造函数名及其参数表,只有在定义函数时才列出。
派生类构造函数的定义中可以省略对基类构造函数的调用,其条件是在基类中必须有默认的构造函数,或者根本没有定义构造函数。当然,基类中没有定义构造函数,派生类根本不必负责调用基类的构造函数。
(2)派生类的析构函数
析构函数也不能被继承,它也需要在派生类的析构函数调用基类的析构函数。派生类中定义的析构函数用来撤销派生类中新增加的成员。基类中的成员仍然由基类的析构函数来处理。
(3)覆盖成员函数
在派生类中重新定义一个在基类中已经定义的成员函数,那么派生类对象调用的是派生类重新定义的成员函数。也就是说,如果派生类中声明一个成员函数,这个成员函数与基类中的一个成员函数相同。定义一个派生类的对象,通过这个对象调用这个成员函数,那么访问的是派生类中的成员函数。
(4)多重继承的二义性
若基类A中有数据成员a, B与C由A派生而来,当D继承基类B与C时,编译系统无法确定访问哪个基类的数据成员a,出现二义性。
可以通过虚基类来解决。为了在派生类中保留一份基类的成员,该基类的所有直接派生类中都应当声明为虚基类。声明虚基类的格式如下:
class派生类名:virtual 继承方式基类名
{…}
C++中规定:编译系统只执行最后一个派生类对虚基类的构造函数的调用,而在执行其他派生类的构造函数时,都不调用虚基类的构造函数。因此,类D的构造函数直接调用虚基类的构造函数,而类B和类C都不调用类A的构造函数。在派生类D的构造函数中,没有给出对虚基类A的调用,因此就调用虚基类A的默认构造函数,所以下面示例代码中,输出x的值为0。如果类D构造函数的初始化列表中添加调用虚基类A的构造函数:
D(inta,int b,int d,int e,int f):A(1),B(a,b),C(d,e)
则输出x的值就为1。示例代码如下:
#include<iostream.h>
class A
{
public:
intx;
A(inta=0){x=a;}
};
class B:virtual public A
{
public:
inty;
B(inta,int b):A(b)
{y=b;}
voidshow()
{cout<<"x="<<x<<'\t'<<"y="<<y<<'\n';}
};
class C:virtual public A
{
public:
intz;
C(inta,int b):A(b)
{z=a;}
voidshows()
{cout<<"x="<<x<<'\t'<<"z="<<z<<'\n';}
};
class D:public B,public C
{
public:
intm;
D(inta,int b,int d,int e,int f):B(a,b),C(d,e)
{m=f;}
voidprint()
{
show();
shows();
cout<<"m="<<m<<'\n';
}
};
int main()
{
Dd(1,2,3,4,5);
d.print();
return0;
}
注:并不是所有的面向对象的程序设计语言都支持多重继承,例如Java和Smalltalk等。因为多重继承比较复杂,程序的编写、调试和维护工作都很困难,容易产生二义性。许多专业人员并不提倡在程序中使用多重继承。
4 多态
多态是指不同的对象在接收同一消息时可以产生完全不同的行为。多态的重要性在于允许一个类体系的不同对象,可以以各自不同内容的函数响应同一消息,实现“同一个接口,多种方法”。
多态的支持可分为两种,静态联编支持静态多态,它是通过函数重载和运算符重载来实现。动态联编支持动态多态,它是通过虚函数来实现的。
(1)运算符的重载
(2)虚函数
在基类把某个成员函数定义为虚函数后,允许在派生类中重新定义该函数,以实现新的功能。如果没有重新定义,派生类只能简单地继承基类的虚函数。需要注意的是:
Ø 虚函数一定是类的成员函数,因为虚函数仅适用于有继承关系的类对象。
Ø 虚函数一定是非静态的成员函数,因为静态的成员函数不受类对象的限制。
Ø 构造函数不能是虚函数,但是通常将析构函数定义为虚函数,以便实现撤销对象时的多态。
Ø 在实现这种动态多态时,必须使用基类的指针或引用。
Ø 在调用虚函数时,执行的速度要比一般成员函数慢,因为如果一个类中含有虚函数时,编译系统就会为类构造虚函数表,用来保存相应虚函数的入口地址,函数的调用是间接实现的。
(3)虚析构函数
在撤销派生类对象时,一般先调用派生类的析构函数,然后再调用基类的析构函数。但是,如果定义了一个指向基类的指针,该指针又指向用new运算符创建的派生类对象,并且基类又有析构函数。则在程序中,用运算符delete撤销对象时,系统只会执行基类的析构函数,而不会执行派生类的构造函数。为了能够保证正确地执行析构函数,就需要定义虚析构函数,这样派生类的析构函数和基类的析构函数都会调用。示例代码如下:
#include<iostream.h>
class A
{
public:
virtual~A(){cout<<"调用A的析构函数。\n";}
};
class B:public A
{
public:
B(inti){buf=new char[i];}
~B()
{
delete[]buf;
cout<<"调用B的析构函数。\n";
}
private:
char*buf;
};
void fun(A *a)
{
deletea;
}
int main()
{
A*a=new B(15);
fun(a);
return0;
}
程序的运行结果:
调用B的析构函数。
调用A的析构函数。
从结果中可以看到,先调用了派生类的析构函数,然后调用了基类的析构函数,如果将基类A中的析构函数不定义成虚析构函数,输出结果就为:
调用A的析构函数。
注:如果一个基类的析构函数被声明为虚析构函数,则它的派生类的析构函数也是虚析构函数,不管它是否使用了关键词virtual,也不管它的函数名是否相同。
(4)纯虚函数
在声明基类时,可能会遇到这样的情况:无法定义基类中虚函数的功能,但是它的派生类都需要使用该虚函数的功能。此时,就可以将基类中的虚函数声明为纯虚函数。纯虚函数是一种特殊的虚函数,声明格式如下:
virtual类型 函数名(参数)=0;
纯虚函数应当注意以下几点:
Ø 纯虚函数没有函数体;
Ø 最后面的“=0”并不代表函数的返回值为0,它只是一种形式,通知编译系统这是一个纯虚函数。
Ø 不能调用纯虚函数。它只是声明这是一个虚函数,具体的实现在派生类中定义。在派生类中对此函数声明后,就可以调用它。
Ø 如果在基类中声明了纯虚函数,而在其派生类中没有对该函数进行声明,则该函数在派生类中仍是纯虚函数。
纯虚函数的示例代码:
#include<iostream.h>
class Virtualbase
{
public:
virtualvoid Demon()=0;
virtualvoid Base(){cout<<"this is farther class"<<endl;}
};
class SubVirtual:public Virtualbase
{
public:
voidDemon(){cout<<"this is SubVirtual"<<endl;}
voidBase(){cout<<"this is subclass Base"<<endl;}
};
void main()
{
Virtualbase*inst=new SubVirtual;
inst->Demon();
inst->Base();
}
注:虚函数和纯虚函数的区别
当定义为纯虚函数时,在子类中必须实现,否则子类还是抽象类;定义为虚函数时,可以不必在子类中实现,由父类指针指向的子类默认执行父类中的代码,当该子类需要另外的实现时,可以重写该虚函数。(如果不定义为虚函数,即使重写,由于指针为父类型,只能执行父类中的代码)
- C 基础知识
- C 基础知识
- C基础知识
- C 基础知识
- c基础知识
- c基础知识
- c基础知识
- [C]基础知识
- c基础知识
- C基础知识
- 汇编基础知识 - [C/C++]
- C语言基础知识
- C/C++一些基础知识
- C语言基础知识1
- c语言基础知识
- C语言基础知识
- c语言基础知识回顾
- C一些基础知识
- 尾递归与Continuation
- MapReduce程序之实现单表关联
- 九度1027 - 数学 - 欧拉回路
- MFC 中 Tooltip 实现的几种方式
- POJ 1113 && HDU 1348 Wall(凸包)
- C++基础知识
- hive之实现列转行
- Linux驱动发开,usb设备的probe全过程
- map问题
- Raspberry Pi 2从裸板到ubuntu14.04,一步一步安装Robot Operating System(机器人操作系统)
- 黑马程序员--java学习笔记第十天
- PowerDesigner简介
- Unity里名字牌、血条等头顶挂件的实现
- 手机屏幕适配