C++成长的足迹

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 = iNumn就为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)一样,在参数中直接写入需要处理的输入变量,同时又可具有指针的数据传递功效,那么定义这个函数时,可以在该参数的类型后面加上一个&符号表示变量引用。

6.      结构体和联合体

基本的数据类型可以为各种不同的数据指定相应的内存分配空间,但是对于一些互相关联的数据,如图象文件,在程序源代码中集中定义,可以起到隔离其它数据的作用,清晰表明程序数据的用途。

结构体中的数据在内存中是连续存放的,与数组不同,结构体中的各个成员变量可以是不同的数据类型,而数组则是相同类型的数据集合。定义的一般形式为:

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("510之和等于%d/n",Sum());   //不用指定是哪两个数相加
        printf("510之差等于%d/n",Minus());//不用指定是哪两个数相减
         
}
程序结果:
510之和等于15
510之差等于-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<<"510的和等于"<<computeObj.Sum()<<endl;
        cout<<"510的差等于"<<computeObj.Minus()<<endl;
        return 0;
}
程序结果如下:
510之和等于15
510之差等于-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: 私有,只有该类的代码可以访问
如果类的变量和函数是从其他类中继承过来的,那么它们在继承类中的权限必须考虑继承方式的权限。一个基本原则是,继承过来的变量函数的访问权限,不能被继承方式的权限放大,否则保持原有的权限。权限是不能放大的
     
     

  
  
   
    
  
  
对于C语言来说,函数是不允许修改被传递变量的值,除非是指针传递。而C++中存在一种情况就是引用

  
  
   
    
  
  
异常处理: exception handling

  
  
   
    
  
  
模板template<class T>
 那么T就是一个模板

  
  
   
    
  
  
顺序点:
或逻辑||,逗号、冒号在C++中都是顺序点,也就是说现计算左侧表达式的值,在计算右侧的值,如果右侧需要计算的话,比如:
i++<6 || i==j; i的值为多少?
两种情况:如果i>=6,那么第一种情况为false,判断右侧的值此时左侧的值i的值+1 ,如果i的值小于6那么则不需判定右侧的值,i的值+1
因此&&也是一个顺序点,比关系运算符优先级低

  
  
   
    
  
  
switch()case语句中,不要用关系运算符,因为得到的结果不是你想要的!
比如如果你想写一个按键关于Y/N的程序,当然区分大小写拉如果你要是这么写
char Put;
cin>>Put;
switch(Put)
{
case Y|y:
        cout<<….
}
这样写的目的是如果Y或者y就执行下面的语句,但是别忘了‘y||Y’是一个表达式它的值为:1,为什么为1呢,也就是问:‘y’为真为假,它当然为真了,妈的!

  
  
   
    
  
  
0是一件很痛苦的事情,可以用逻辑运算表达式来避免:
比如:x!=0 && 1/x<100 前提就是避免了x==0
可以这么用if(x!=0 && 1/x<100){/**/}

  
  
   
    
  
  

  
  
   
    
  
  
对于图像的循环提取
CvCapture* capture = cvCapatureFromCam(-1);
//IplImage* img = cvQueryFrame(capture);
for(;;)
{
       Img = cvQueryFrame(capture);
}
这是一个无限循环
如果对于文件来说,有个结尾,并且不想规定它读取的帧数,可以用img来判断,因为img如果query到的话,其值为真,如果query不到的话,其值为NULL,所以可以用while(img = cvQueryFramecapture))来实现,也可以用
for(int fr = 0; img; img = cvQueryFrame(capture), fr++)
{}
来实现,如果img读到文件尾,则imgNULL,循环结束。

  
  
   
    
  
  
C++函数不能返回数组,但是可以其它任何类型的值,如int.float.struct和对象,有趣的是,C++可以将数组作为结构体或者对象中的组成部分来返回。也就是说不返回数组,但可以返回数组的首地址。

#include <iostream>

 

using namespace std;

 

int* fun(int a[], int* b);

 

int main()

{

       int a[10] = {0};

       int* b = a;

       int* c = fun(a, b);

       for(int i=0; i<10; i++)

       {

              cout<<c[i]<<endl;

       }

       return 0;

}

 

int* fun(int a[10], int* b)

{

      //return a;       //right

       return b;

}
    
    

  
  
   
    
  
  
为什么需要函数原型:原型描述了函数到编译器的接口。也就是说它将函数的返回值的类型以及函数参数的类型和数量告诉编译器。
     
     
C++的函数原型和ANSI C的函数原型:在C++中函数原型是必不可少的,也就是说即使没有,也要加上void,但在ANSI C中如果为空,并不表示没有参数,而是表示参数可选。既可以在函数定义后加上参数定义。
     
     
在编译阶段进行原型化被称为静态类型检查(static type checking.)
     
     
C++中参数argument表示实参,使用参量parameter表示行参。
     
     
为了使数组通用,不限定特定长度的数组,还需要传递数组长度。
     
     

  
  
   
    
  
  
在大多数情况下,C++C语言一样,也将数组名视为指针。C++将数组名解释为第一个元素的地址。但是有两种例外情况:第一种情况是数组声明使用数组名标记存储位置;第二种情况是对数组名的取值使用sizeof将的到整个数组的长度。也就是说数组名不只是一个元素的地址,并且它标记存储位置,和纪录数组长度。
     
     
如此的函数声明 int sum(int arr[], int length)并不是表示数组只能这么定义,而是可以是数组通用,并不限定它的长度。
     
     

  
  
   
    
  
  
C++传递常规变量时,函数将使用该变量的拷贝,但传递数组时,函数使用原来的数组。但实际上,这种传递方法并不影响C++按值传递的过程,数组仍然传递一个值,这个值被赋予了一个新的变量但这个值是地址。
     
     

从这可以看出数组传递的是地址,也就是说数组是可以在子函数中被改变的

但是常规变量不能,也就是类似于指针传递过程,因此,对于数组作参数,

并且你不想让原数组被改动的化,最好加上const,在函数传递中也要加上一个const
     
     

  
  
   
    
  
  

/******************************************************************

#include <IOSTREAM>

 

using namespace std;

 

const int a = 5;

 

int main()

{

       int b = a;

       int c = 5;

       const int d = c;

      

       return 0;

}

*******************************************************************/

 

#include <IOSTREAM>

 

const int ArSize = 8;

 

int sum_arr(int arr[], int n);

 

int main()

{

       int cookies[ArSize] = {1, 2, 4, 8, 16, 32, 64, 128};

       std::cout<< cookies << " = array addresss, ";

       std::cout<< sizeof cookies <<" = sizeof cookies/n ";

 

       int sum = sum_arr(cookies, ArSize);

       std::cout<< "Total cookies eaten: "<< sum << std::endl;

 

       sum = sum_arr(cookies, 3);

       std::cout<< "First three eaters ate "<< sum <<"cookeis. /n";

 

       return 0;

}

 

int sum_arr(int arr[], int n)

{

       int total = 0;

 

       std::cout<<arr<<" = arr, ";

       std::cout<< sizeof arr << " = sizeof arr/n ";

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

       {

              total = total = arr[i];

       }

       return total;

}

 

/***********************************************************

//cmd answer:

0012FF60 = array addresss, 32 = sizeof cookies

 0012FF60 = arr, 4 = sizeof arr

 Total cookies eaten: 128

0012FF60 = arr, 4 = sizeof arr

 First three eaters ate 4cookeis.

Press any key to continue

值的注意的是对cookiessizof是数组的长度,而对arr取地址是

整数字节的长度-指针变量的长度。这也是显示传递数组长度的原因

也就是说不传递长度时,子函数是不知道长度的

************************************************************/
    
    

  
  
   
    
  
  
可以将非const 或者const类型的数据赋予指向const类型的指针,但是不可以将const类型的数据赋予指向非const类型的指针。因此对于函数,行参argument为非const类型,而实参为const类型,对于数组来说是不允许的。

  
  
   
    
  
  
尽可能的使用const,原因:将指针声明为指向常量数据的指针有两条理由:
这样可以避免由于无意间修改数据而导致的编程错误。
使用const可以使函数能够处理const和非const的实参,否则只能接受非const数据。

  
  
   
    
  
  
还有一个要说明的地方,并不是说:
int a = 10;
const int *pt = &a;
*pt的值就无法改变,因为虽然声明了*ptconst,但是pt的地址可以更改,因此我可以定义 int b = 30 ; pt = &b;这样pt的地址可以更改,因此其*pt的值也发生变化,*pt = 30;
因此生命的方式为const int* const pt = &a

  
  
   
    
  
  
函数和二维数组:即二维数组做函数参数问题:
为编写将二维数组作为参数的函数,数组名被认为是其地址,因此相应的实参是一个指针,而不是一个指针数组,因此二维数组做函数指针的时候,比较难处理的是如何正确的声明指针。我知道这么定义数组是正确的:
int sum(int a[][4], int size);
由此可以看出形参表示的是一个指针,而不是一个数组。因为传递数组它将很耗费资源。所以如果用指针的形式表示应该是:
int sum(int (*a)[4], int size);
这里就涉及到一个问题:*a[4], (*a)[4]的区别,前者即和*(a[4])相同,表示的是一个指针数组,即表示四个指向整形类型的指针,因此是一个指针的数组。
后者表示整形指针,这个指针指向有四个整形变量的数组,意思是表示指向一个含有四个整形变量的数组的指针,也就是说前者是四个指针,代表四个一维数组,而后者表示的是一个指针,指向一个二维数组。
这让我想起了new一个多维数组的问题:msdn中的
float (*cp)[25][10];
     
     
cp = new float[10][25][10];
     
     
(*cp)[25][10]表示的一个指向以有2510列表示的面的数组的首地址,也就是说指针表示一个地址,而不是一个指针数组,他表示一个数组指针!
顺便说一下,删除的时候:
Delete []cp,即可。
对于二维数组的使用方式是:
可以使用数组表示法,原因如下:由于a指向数组(他的元素是由四个int组成的数组)的第一个元素,因此表达式a+r指向编号为r的元素。因此a[r]是编号为r的元素。由于该元素是一个有4int组成的数组,因此a[r]是由4int组成的数组的名称。将下标用于数组名将得到一个数组元素,因此a[r][c]是由4int组成的数组中的一个元素,是一个int值。必须对a执行两次解除引用,才能得到数据。最简单的方法是使用方括号两次:a[r][c],如果不考虑难看的化,也可以使用操作符*两次:
a[r][c] = *(*(a + r) + c);
a 表示 : 指向一个含有4int类型的数组的第一行;
a+r  表示: 指向含有4int类型数组的第r行;
*(a + r) 表示:表示指向第r行的第一个整形元素
*(a+r) + c 表示指向第r行,第c个元素
*(*(a+r)+c) 表示第r,c个元素
是一个指向是有多么的区别!
但是在opencv中对于一个图像的元素位置的定义为什么是:
((uchar*)(image2->imageData+image2->widthStep*j))[i]
    
    
不知道为什么。
     
     
*((uchar*)(image2->imageData + i) +  j*image2->widthStep )
    
    
这么定义也对。
     
     
((uchar*)(image2->imageData + i))[j*image2->widthStep]
    
    
这么定义也对,但是就是这么定义:
     
     
image2->imageData[i][j*image2->widthStep]是不对的。
 
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值