1.
在源文件所有函数之外定义的变量称为全局变量,全局变量可以被所有源文件中的函数访问,它所占据的内存在程序执行期间一直存在,不会被释放。如果其它源文件需要使用在某个源文件中定义的全局变量时,要用
extern
关键字来修饰这个全局变量,说明变量已经在其它源文件中定义。如果在多个源文件中定义了相同名字的全局变量,则提示变量重复定义的错误。
2.
函数
一般来说,函数是通过
return
语句返回处理后的数据的,作用是数据输出,即压入到堆栈中,将函数调用后的返回值赋给某一变量,就是从堆栈中取出结果值。这是函数与外界交互的一个主要途径,即使用堆栈。
3.
在变量前加上
static
修饰符,可以产生
static
全局变量和
static
局部变量。
static
全局变量虽然是全局的,但不能被其它的源文件通过
extern
进行引用访问。
static
局部变量虽然具有局部性质,仅能被定义它的块内语句使用,但是当它的值在函数执行完毕,仍可以保留下来,下次再调用该函数访问该变量时,将使用上次函数执行后的变量值。
4.
指针变量用来存储某个地址的值,为了避免指针的危害,
C++
也提供了引用的用法,
力求以更直观的同一个对象的指称在形式上遮蔽直接的指针变量使用。只用指针变量可以解决大量数据到函数的传递问题。
5.
将函数执行地址保存起来的变量成为函数指针变量,它的定义并不是按照一般的数据指针变过量的“类型
变量名”形式来定义的,而是采用接近于函数原形的形式来定义的,即
数据类型
(*
函数指针变量名
)(
参数列表
)
,以便编译器实行相关的赋值检查,下面为具体函数地址赋予函数指针变量。
int (*pFunc)(int); //
定义一个函数指针变量
int Check(int); //
一个具体的函数
pFunc = Check; //
将函数
Check
的执行地址赋值给函数指针变量
pFunc
函数指针变量的一个很大的作用就是,可以把函数作为函数的参数。
看下面的例子:
//
如何将函数作为函数参数
#include <stdio.h>
#include <math.h>
//
函数原型声明
double Sum(double (*pFunc)(double), double dStart, double dEnd);
//
主函数
void main()
{
double dSum;
dSum = Sum(sin, 0.3,1.0);
printf("
对位于
[0.3
,
1.0]
,范围内的角求
sin
和,
sin
和
= %f /n",dSum);
dSum = Sum(cos, 0.3,1.0);
printf("
对位于
[0.3
,
1.0]
,范围内的角求
cos
和,
cos
和
= %f /n",dSum);
}
//
子函数
double Sum(double (*pFunc)(double), double dStart, double dEnd)
{
double dSum = 0.0;
for(double d = dStart; d<dEnd; d+=0.1)
dSum += pFunc(d);
return dSum;
}
输出结果:
对位于
[0.3
,
1.0]
,范围内的角求
sin
和,
sin
和
= 4.715378
对位于
[0.3
,
1.0]
,范围内的角求
cos
和,
cos
和
= 6.202777
Press any key to continue
利用函数指针的另一个应用是:对于需要进行大量的分支处理程序,典型的做法是将相关处理程序全部集中在一个非常大的
switch
块中来进行,但会显得非常臃肿,不便于与其它模块进行交互,如果将所处理的函数的函数指针存放在数组或者堆栈中,然后通过取函数指针的方式进行函数的调用,可以大大提高代码的灵活性。如编译器的状态机处理和角色各种动作状态的处理都可以应用函数指针得到较好的灵活性。
#define N 100;
int people[N];
int (*pFunc)(int);
int move(int); //
已经定义好的动作
pFunc = move;
people[0] = pFunc;
//
这个好像不对,将一个指针存放在数组里,怎么存放呢?
//
应该是正确的,也就是说
people[0]
里存放的是函数指针变量的地址。
作为指针替代的引用,形式上与指针有很大的区别,主要体现在引用只是用作某个变量的别名,并没有为声明的引用名分配内存。在引用声明的同时,必须进行初始化,格式为:数据类型
&
引用名
=
某个变量,如下所示:
int iNum = 10;
int &n = iNum; //
注意
n
不是
iNum
的地址,而是可以看作为就是
iNum(
别名么
^-^)
,既然它具有指针的特性,则
n = *p = iNum
,
n
就为
iNum
的指针。
C++
提供引用的用法,主要是为了避免直接将指针地址作为参数值传递给函数,从而减低使用指针带来的风险。函数参数使用引用传递,最终编译后,仍然是使用指针来传递的,这样又利用了指针高效的数据传递能力。
#include "iostream.h"
void Add10(int &x) //
使用引用作参数
,
不要以为是取地址!!
{
x+=10;
}
void main()
{
int n = 3;
cout<<"
使用引用作参数的用法
"<<endl;
Add10(n);
cout<<n<<endl;
}
输出结果:
使用引用作参数的用法
13
Press any key to continue
结论:如果一个函数希望像上面的
Add10(n)
一样,在参数中直接写入需要处理的输入变量,同时又可具有指针的数据传递功效,那么定义这个函数时,可以在该参数的类型后面加上一个
&
符号表示变量引用。
1.
结构体和联合体
基本的数据类型可以为各种不同的数据指定相应的内存分配空间,但是对于一些互相关联的数据,如图象文件,在程序源代码中集中定义,可以起到隔离其它数据的作用,清晰表明程序数据的用途。
结构体中的数据在内存中是连续存放的,与数组不同,结构体中的各个成员变量可以是不同的数据类型,而数组则是相同类型的数据集合。定义的一般形式为:
struct
结构体名
{
数据类型
成员变量
1
;
数据类型
成员变量
2
;
数据类型
成员变量
N
;
}
;
结构体已经定义就可以用来定义具有该结构的变量,一般的使用形式为
struct
结构体名
变量名
;
上面定义的结构体变量,仍需使用
struct
关键字,十分的不方便,可应用
C
语言提供的类型定义,将结构体类型定义成一个新的数据类型,这样定义该类型的变量时,就可以省去书写
struct
的麻烦。例如:
typedef struct struc_data_tag
{
char name[8];
char sex;
int year;
}struc_data;
这时定义
struc_data
类型的变量,就不用在前面加上
struct
关键字,与普通的类型变量保持一致。如
:
Struc_data student1;
那么在
C++
中,已经允许声明结构体变量,不需要在前面加上
struct
关键字,而且可在
C++
的结构体中定义函数,这种结构体相当于变量和函数都是
public
的一个类。
在
opencv
中定义
IplImage
的数据类型如下方式:
typedef struct _IplImage
{
int nSize; /* sizeof(IplImage) */
int ID; /* version (=0)*/
int nChannels; /* Most of OpenCV functions support 1,2,3 or 4 channels */
int alphaChannel; /* ignored by OpenCV */
int depth; /* pixel depth in bits: IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16U,
IPL_DEPTH_16S, IPL_DEPTH_32S, IPL_DEPTH_32F and IPL_DEPTH_64F are supported */
char colorModel[4]; /* ignored by OpenCV */
char channelSeq[4]; /* ditto */
int dataOrder; /* 0 - interleaved color channels, 1 - separate color channels.
cvCreateImage can only create interleaved images */
int origin; /* 0 - top-left origin,
1 - bottom-left origin (Windows bitmaps style) */
int align; /* Alignment of image rows (4 or 8).
OpenCV ignores it and uses widthStep instead */
int width; /* image width in pixels */
int height; /* image height in pixels */
struct _IplROI *roi;/* image ROI. when it is not NULL, this specifies image region to process */
struct _IplImage *maskROI; /* must be NULL in OpenCV */
void *imageId; /* ditto */
struct _IplTileInfo *tileInfo; /* ditto */
int imageSize; /* image data size in bytes
(=image->height*image->widthStep
in case of interleaved data)*/
char *imageData; /* pointer to aligned image data */
int widthStep; /* size of aligned image row in bytes */
int BorderMode[4]; /* border completion mode, ignored by OpenCV */
int BorderConst[4]; /* ditto */
char *imageDataOrigin; /* pointer to a very origin of image data
(not necessarily aligned) -
it is needed for correct image deallocation */
}IplImage;
那么定义时用IplImage *image即可,而不是struct IplImage* image;
联合体union又称共用体,共用体的意思就是各种数据类型享用同一块存储空间。也就是说他们的空间类型是其中的一种,而不是每种都代替。定义方式:
union 联合体名
{
数据类型
成员变量
1
;
数据类型
成员变量
2
;
数据类型
成员变量
N
;
}
;
联合体类型所占用的内存空间,是其最长的成员变过量所占的存储空间,而不是其所有变量占用内存空间的总和。
联合体的用法:联合体一般与结构体结合使用,例如:定义一个数据类型的
datatype
标志和一个可存放多种类型数据的联合体,程序可以根据
datatype
的标志,进行相应的数据处理
typedef struct sturct_data_tag{
int datatype;
union{
int iValue;
float fValue;
char* pValue;
}
}process
process process1
if(process1.datatape ==INT)
{
//process
}
if(process1.datatape == FLOAT)
{
//process
}
在
opencv
中对
CvMat
的定义如下:
typedef struct CvMat
{
int type; /* CvMat signature (CV_MAT_MAGIC_VAL), element type and flags */
int step; /* full row length in bytes */
int* refcount; /* underlying data reference counter */
union
{
uchar* ptr;
short* s;
int* i;
float* fl;
double* db;
} data; /* data pointers */
#ifdef __cplusplus
union
{
int rows;
int height;
};
union
{
int cols;
int width;
};
#else
int rows; /* number of rows */
int cols; /* number of columns */
#endif
} CvMat;
7. 类的使用
C语言程序给予模块对任务进行分解和人员的分工,每个模块包含了模块变量和模块函数,模块的最主要的构成元素是函数。C语言的调用方式如下:
Compute模块的头文件compute.h代码如下:
//函数原型声明
void Init(int, int);
int Sum();
int Minus();
compute模块的实现文件compute.cpp代码如下:
int i;
int j;
void Init(int m, int n)
{
i = m;
j = n;
}
int Sum()
{
return i+j;
}
int Minus()
{
return i-j;
}
Main主模块的实现文件main.cpp代码如下:
#include <stdio.h>
#include "compute.h"
void main()
{
Init(5, 10);
printf("5和10之和等于%d/n",Sum()); //不用指定是哪两个数相加
printf("5和10之差等于%d/n",Minus());//不用指定是哪两个数相减
}
程序结果:
5和10之和等于15
5和10之差等于-5
Press any key to continue.
用C++来实现同样的功能:
CCompute类的头文件Compute.h代码如下:
class CCompute
{
protected:
int i;
int j;
public:
CCompute();
~CCompute();
void Init(int m, int n);
int Sum();
int Minus();
}; //一个类的定义结束要有分号!! 否则出现的错误你都不知道在哪里寻找。
CCompute类的实现文件Compute.cpp代码如下:
#include "Compute.h" //要包含头文件,c模式下.h .cpp是自动对应的c++中不是,最好都加上。
CCompute::CCompute(){
}
CCompute::~CCompute()
{
}
void CCompute::Init(int m, int n) //类型标示不要忘了
{
i = m;
j = n;
}
int CCompute::Sum()
{
return i+j;
}
int CCompute::Minus()
{
return i-j;
}
main.cpp的文件代码如下:
#include <iostream.h>
#include "Compute.h"
int main()
{
CCompute computeObj; //创建对象
computeObj.Init(5, 10); //调用时就不需要类型标示了
cout<<"5和10的和等于"<<computeObj.Sum()<<endl;
cout<<"5和10的差等于"<<computeObj.Minus()<<endl;
return 0;
}
程序结果如下:
5和10之和等于15
5和10之差等于-5
Press any key to continue.
这种算不上经典的小程序其实可以帮助我们更好的了解什么是类及类的用法,不会使人避而远之。
C++允许在类中定义同名的函数,而函数的形参和返回类型可以不同,这就是所谓的函数重载技术。重载函数解决了大量意义相同的函数命名问题。
对一个类的函数代码的修改,可以在继承类中定义同名、同参数列表和同返回值类型的函数来实现,这就是C++的覆盖技术。被覆盖的函数,要求在基类中用virtual修饰,即声明为序函数。一旦函数在一个类中声明为序函数,在该类的继承类中定义的函数也是虚函数,不必再用virtual进行修饰。也就是说重载的虚函数也为虚函数。
需要说明的是,即使基类的函数不用virtual修饰,通过继承类的对象调用函数,都可以正常执行继承类的函数,而不是基类的函数。但如果将继承类的对象转换成基类的对象进行调用,没有声明为virtual的函数,实际执行的基类的函数。
虚函数的继承函数是对基类虚函数的覆盖,而继承类的函数则是隐藏了基类的非虚函数,一般不建议在继承类中直接重新定义基类的非虚函数,既不提倡使用函数隐藏,以免产生非预期的执行结果。
如下例子:
#include <stdio.h>
//base class
class CBase
{
public:
virtual void f(int x)
{
printf("CBase::f函数打印: 整数%d/n",x);
}
void g(float x)
{
printf("CBase::g函数打印:浮点数%f/n",x);
}
};
//derived class
class CDerived:public CBase
{
public:
//对虚函数来数,就是对基类虚函数的覆盖
void f(int x)
{
printf("CDerived::f函数打印:整数%d/n",x);
}
/*
非基函数,则是意味着隐藏,也就是说对于继承类的对象,他们的效果相同
而对于将继承类转换成基类的对象进行调用,没有声明为virtual,实际上
执行的是基类的函数。
*/
void g(float x)
{
printf("CDerived::g函数打印:浮点数%f/n");
}
};
//main
void main()
{
CDerived DerivedObj;
DerivedObj.f(3);
DerivedObj.g(6.0f);
CBase* pBaseObj = &DerivedObj;
pBaseObj->f(3);
pBaseObj->g(6.0f);
}
结果如下:
CDerived::f函数打印:整数3
CDerived::g函数打印:浮点数0.000000
CDerived::f函数打印:整数3
CBase::g函数打印:浮点数6.000000
Press any key to continue
这说明,如果你定义的类有要继续修改下去的可能的话,则最好将函数定义为虚函数,但是所有的函数基本上都需要进行修改,那就统统定义成虚函数吧:)
此外要注意的是:如果在虚函数的声明之后加上 “=0”的标记,那么该虚函数成为纯虚函数。若一个类包含纯虚函数的定义,该类成为抽象类。抽象类不能创建对象,她的继承类必须提供纯虚函数的具体实现。
继承的方式:
public:公有 任何代码都可以访问
protected: 保护,该类和派生类可以
private: 私有,只有该类的代码可以访问
如果类的变量和函数是从其他类中继承过来的,那么它们在继承类中的权限必须考虑继承方式的权限。一个基本原则是,继承过来的变量函数的访问权限,不能被继承方式的权限放大,否则保持原有的权限。权限是不能放大的