c++难点(较高级使用技巧)总结

C++高级编程篇-如何编写高效的C++篇 为了编写高效的程序,需要在设计层次上做考虑,并在实现层次上考虑细节。(一定要在程序的生命周期已开始就考虑性能)。 “编写高效的C++程序”指要编写能高效运行的程序,而不是高效地编写程序,要学会如何节省用户的时间,而不是节省你自己的时间。 ​ 1、实现高效的两种方法 如果想编写真正高性能的应用,就必须从设计一开始就考虑效率问题。设计级效率包括:选择高效算法,避免不必要的步骤和计算,而且要选择... 阅读详情

一、数组名和指针的深入理解

指针是C/C++语言的特色,而数组名与指针有太多的相似,甚至很多时候,数组名可以作为指针使用。于是乎,很多程序设计者就被搞糊涂了。

魔幻数组名

请看程序(本文程序在WIN32平台下编译):

复制代码
#include <iostream>
using namespace std;

int main()
{
    char str[10];
    char* pStr = str;
    cout << "sizeof(str): \t" << sizeof(str) << endl;
    cout << "sizeof(pStr): \t" << sizeof(pStr) << endl;
    
    system("pause");
    return 0;
} 
复制代码

 
1、数组名不是指针

我们先来推翻"数组名就是指针"的说法,用反证法。

证明 数组名不是指针

假设:数组名是指针;

则:pStr和str都是指针;

因为:在WIN32平台下,指针长度为4;

所以:sizeof(str)和sizeof(pStr)的输出都应该为4;

实际情况是:

图片内容为:

sizeof(str):10

sizeof(str):4

所以:假设不成立,数组名不是指针


2、数组名神似指针

上面我们已经证明了数组名的确不是指针,但是我们再看看程序的这一:char* pStr = str;。该行程序将数组名直接赋值给指针,这显得数组名又的确是个指针!

我们还可以发现数组名显得像指针的例子:

复制代码
#include <iostream>
using std::cout;
using std::endl;

int main()
{
    char str1[10] = "I love U.";
    char str2[10];
    
    strcpy(str2, str1);
    
    cout << "string array 1: " << str1 << endl;
    cout << "string array 2: " << str2 << endl;
    
    system("pause");
    return 0;
}
复制代码


标准C库函数strcpy的函数原形中能接纳的两个参数都为char型指针,而我们在调用中传给它的却是两个数组名!

函数输出:

图片内容为:

string array 1:I Love U.

string array 2:I Love U.

数组名再一次显得像指针!

既然数组名不是指针,而为什么到处都把数组名当指针用?


揭密数组名(对上面的总结)

现在到揭露数组名本质的时候了,先给出三个结论:

(1)数组名的内涵在于其指代实体是一种数据结构,这种数据结构就是数组;

(2)数组名的外延在于其可以转换为指向其指代实体的指针,而且是一个指针常量;

(3)指向数组的指针则是另外一种变量类型(在WIN32平台下,长度为4),仅仅意味着数组的存放地址!

1、数组名指代一种数据结构:数组(数组也是一种数据结构)

现在可以解释为什么第1个程序的这一行:sizeof(str);的输出为10的问题,根据结论1,数组名str的内涵为一种数据结构,即一个长度为10的char型数组,所以sizeof(str)的结果为这个数据结构占据的内存大小:10字节。

再看:

1. int intArray[10];
2. cout << sizeof(intArray) ;


第2行的输出结果为40(整型数组占据的内存空间大小)。

如果C/C++程序可以这样写:

1. int[10] intArray;
2. cout << sizeof(intArray) ;


我们就都明白了,intArray定义为int[10]这种数据结构的一个实例,可惜啊,C/C++目前并不支持这种定义方式。

2、数组名可作为指针常量

根据结论2,数组名可以转换为指向其指代实体的指针,所以程序1中的第5行数组名直接赋值给指针,程序2第7行直接将数组名作为指针形参都可成立。

下面的程序成立吗?

1. int intArray[10];
2. intArray++;
读者可以编译之,发现编译出错。原因在于,虽然数组名可以转换为指向其指代实体的指针,但是它只能被看作一个指针常量,不能被修改。

而指针,不管是指向结构体、数组还是基本数据类型的指针,都不包含原始数据结构的内涵,在WIN32平台下,sizeof操作的结果都是4。
顺 便纠正一下许多程序员的另一个误解。许多程序员以为 sizeof是一个函数,而实际上,它是 一个操作符,不过其使用方式看起来的确太像一个函数了。语句sizeof(int)就可以说明sizeof的确不是一个函数,因为函数接纳形参(一个变量),世界上没有一个C/C++函数接纳一个数据类型(如int)为"形参"。

3、数组名可能失去其数据结构内涵 (变成一个很平民的指针)

请看下面一段程序:
复制代码
#include <iostream>
using std::cout;
using std::endl;

void arrayTest(char str[])
{
     cout << "sizeof(str):" << sizeof(str) << endl;
}

int main()
{
    char str1[10] = "I love U.";
    arrayTest(str1);
    
    system("pause");
    return 0;
}
复制代码

程序的输出结果为:

图片内容为:

sizeof(str):4
一个可怕的数字,前面已经提到其为指针的长度!

结论1指出,数组名内涵为数组这种数据结构,在arrayTest函数体内,str1是数组名,那为什么sizeof的结果却是指针的长度?这是因为:

(1)数组名作为函数形参时,在函数体内,其失去了本身的内涵,仅仅只是一个指针;

(2)很遗憾,在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。

所以,数组名作为函数形参时,其全面沦落为一个普通指针!它的贵族身份被剥夺,成了一个地地道道的只拥有4个字节的平民。
参考:http://www.cnblogs.com/wuzhenbo/archive/2012/05/29/2523777.html

 

 二、函数名与函数指针


一、 通常的函数调用

    一个通常的函数调用的例子:

复制代码
#include <iostream>
using std::cout;
using std::endl;

void MyFun(int x);      //此处也可以声明为:void Myfun(int); int main()
{
    MyFun(10);          //这里是调用MyFun(int x);函数 
    
    system("pause");
    return 0;
}

void MyFun(int x) 
{
      cout << "x: " << x << endl;
}
复制代码


    这个MyFun函数是一个无返回值的函数,他相当于把MyFun函数的主体放到主函数中。看主函数中调用MyFun函数的书写格式:MyFun(10);
  思考:函数名到底又是什么东西呢?
    (不要以为这是没有什么意义的事噢!)

二、 函数指针变量的声明
    就象某一数据变量的内存地址可以存储在相应的指针变量中一样,函数的首地址也以存储在某个函数指针变量里的。这样,我就可以通过这个函数指针变量来调用所指向的函数了。
    在C++系列语言中,任何一个变量,总是要先声明,之后才能使用的。那么,函数指针变量也应该要先声明吧?那又是如何来申明呢?以上面的例子为例,来声明一个可以指向MyFun函数的函数指针变量FunP。

  申明FunP变量的方法:void (*FunP)(int) ;   也可写成void (*FunP)(int x);

  整个函数指针变量的申明格式如同函数MyFun的申明处一样,只不过,把MyFun改成(*FunP)而已,这样就有了一个能指向MyFun函数的指针FunP了。(当然,这个FunP指针变量也可以指向所有其它具有相同参数及返回值的函数了。)

三、 通过函数指针变量调用函数

  有了FunP指针变量后,我们就可以对它赋值指向MyFun,然后通过FunP来调用MyFun函数了。

  如何通过FunP指针变量来调用MyFun函数的:

复制代码
#include <iostream>
using std::cout;
using std::endl;

void MyFun(int x);      //此处也可以声明为:void Myfun(int); 
void (*FunP)(int);   //声明一个用以指向同样参数,返回值函数的指针变量

int main()
{
    MyFun(10);          //这里是调用MyFun(int x);函数 
    FunP = &MyFun;      //将MyFun函数的地址赋给FunP变量 
    (*FunP)(20);        //这是通过函数指针变量FunP来调用MyFun函数的 
    system("pause");
    return 0;
}

void MyFun(int x)       //MyFun函数的定义 
{
      cout << "x: " << x << endl;
}
复制代码
    请看黑体字部分的代码及注释。
    运行看看。
    给我们的感觉是:MyFun与FunP的类型关系类似于int 与int *的关系。函数MyFun好像是一个如int的变量(或常量),而FunP则像一个如int *一样的指针变量。
  int i,*pi;
  pi=&i;    //与FunP=&MyFun比较。(你的感觉呢?)其实不然——

四、 调用函数的其它书写格式
函数指针也可如下使用,来完成同样的事情:
复制代码
#include <iostream>
using std::cout;
using std::endl;

void MyFun(int x);      //此处也可以声明为:void Myfun(int); 
void (*FunP)(int);      //申明一个用以指向同样参数,返回值函数的指针变量

int main()
{
    MyFun(10);          //这里是调用MyFun(int x);函数 
    FunP = MyFun;      //将MyFun函数的地址赋给FunP变量 
    (*FunP)(20);        //这是通过函数指针变量FunP来调用MyFun函数的 
    system("pause");
    return 0;
}

void MyFun(int x)       //MyFun函数的定义 
{
      cout << "x: " << x << endl;
}
复制代码
改了黑体字部分(请自行与之前的代码比较一下)。
运行试试,啊!一样地成功。
啊?
FunP=MyFun;
可以这样将MyFun值同赋值给FunP,难道MyFun与FunP是同一数据类型(即如同的int 与int的关系),而不是如同int 与int*的关系了?(有没有一点点的糊涂了?)
看来与之前的代码有点矛盾了,是吧!所以嘛!
 

请容许暂不给你解释,继续看以下几种情况(这些可都是可以正确运行的代码哟!):
代码之三:
复制代码
#include <iostream>
using std::cout;
using std::endl;

void MyFun(int x);      //此处也可以声明为:void Myfun(int); 
void (*FunP)(int);      //申明一个用以指向同样参数,返回值函数的指针变量

int main()
{
    MyFun(10);          //这里是调用MyFun(int x);函数 
    FunP = &MyFun;      //将MyFun函数的地址赋给FunP变量 
    FunP(20);           //这里是通过函数指针变量来调用MyFun函数的 
    
    system("pause");
    return 0;
}

void MyFun(int x)       //MyFun函数的定义 
{
      cout << "x: " << x << endl;
}
复制代码

 
代码之四:

复制代码
#include <iostream>
using std::cout;
using std::endl;

void MyFun(int x);      //此处也可以声明为:void Myfun(int); 
void (*FunP)(int);      //申明一个用以指向同样参数,返回值函数的指针变量

int main()
{
    MyFun(10);          //这里是调用MyFun(int x);函数 
    FunP = MyFun;      //将MyFun函数的地址赋给FunP变量 
    (*FunP)(20);           //这里是通过函数指针变量来调用MyFun函数的 
    
    system("pause");
    return 0;
}

void MyFun(int x)       //MyFun函数的定义 
{
      cout << "x: " << x << endl;
}
复制代码
真的是可以这样的噢!
(哇!要晕倒了!)
还有呐!看:
复制代码
#include <iostream>
using std::cout;
using std::endl;

void MyFun(int x);      //此处也可以声明为:void Myfun(int); 
void (*FunP)(int);      //申明一个用以指向同样参数,返回值函数的指针变量

int main()
{
    (*MyFun)(10);          //看,函数名MyFun也可以有这样的调用格式 
    FunP = MyFun;          //将MyFun函数的地址赋给FunP变量 
    (*FunP)(20);           //这里是通过函数指针变量来调用MyFun函数的 
    
    system("pause");
    return 0;
}

void MyFun(int x)       //MyFun函数的定义 
{
      cout << "x: " << x << endl;
}
复制代码
你也许第一次见到吧:函数名调用也可以是这样写的啊!(只不过我们平常没有这样书写罢了。)
那么,这些又说明了什么呢?
嗯,依据以往的知识和经验来推理本篇的“新发现”,必定会由此分析并推断出以下的结论:
1. 其实,MyFun的函数名与FunP函数指针都是一样的,即都是函数指针。 MyFun函数名是一个函数指针常量,而FunP是一个函数数指针变量,这是它们的关系。
2. 但函数名调用如果都得如(*MyFun)(10);这样,那书写与读起来都是不方便和不习惯的。所以 C语言的设计者们才会设计成又可允许MyFun(10);这种形式地调用(这样方便多了并与数学中的函数形式一样,不是吗?)。
3. 为统一起见,FunP函数指针变量也可以FunP(10)的形式来调用。
4. 赋值时,即可FunP=&MyFun形式,也可FunP=MyFun。
上述代码的写法,随便你爱怎么着!
请这样理解吧!这可是有助于你对函数指针的应用喽!
最后——
补充说明一点:在函数的声明处:
void MyFun(int );    //不能写成void (*MyFun)(int )。
void (*FunP)(int );   //不能写成void FunP(int )。
(请看注释)这一点是要注意的。

五、 定义某一函数的指针类型:
就像自定义数据类型一样,我们也可以先定义一个函数指针类型,然后再用这个类型来申明函数指针变量。
我先给你一个自定义数据类型的例子。
typedef int* PINT;    //为int* 类型定义了一个PINT的别名
int main()
{
  int x;
  PINT px=&x;   //与int * px=&x;是等价的。PINT类型其实就是int * 类型
  *px=10;       //px就是int*类型的变量 
  return 0;
}
根据注释,应该不难看懂吧!(虽然你可能很少这样定义使用,但以后学习Win32编程时会经常见到的。)
下面我们来看一下函数指针类型的定义及使用:(请与上对照!)
复制代码
#include <iostream>
using std::cout;
using std::endl;

void MyFun(int x);          //此处也可以声明为:void Myfun(int); 
typedef void (*FunType)(int);    //这样只是定义一个指针类型 
FunType FunP;               //然后用FunType类型来声明全局Funp变量 

int main()
{
    //FunType Funp;        //函数指针变量当然也可以是局部的,那就请在这里声明 
    (*MyFun)(10);          //看,函数名MyFun也可以有这样的调用格式 
    FunP = &MyFun;          //将MyFun函数的地址赋给FunP变量 
    (*FunP)(20);           //这里是通过函数指针变量来调用MyFun函数的 
    
    system("pause");
    return 0;
}

void MyFun(int x)       //MyFun函数的定义 
{
      cout << "x: " << x << endl;
}
复制代码

看黑体部分:
首先,在void (*FunType)(int ); 前加了一个typedef 。这样只是定义一个名为FunType函数指针类型,而不是一个FunType变量。
然后,FunType FunP;  这句就如PINT px;一样地申明一个FunP变量。
其它相同。整个程序完成了相同的事。
这样做法的好处是:
有了FunType类型后,我们就可以同样地、很方便地用FunType类型来声明多个同类型的函数指针变量了。如下:
FunType FunP2;
FunType FunP3;
//……

六、 函数指针作为某个函数的参数:
既然函数指针变量是一个变量,当然也可以作为某个函数的参数来使用的。所以,还应知道函数指针是如何作为某个函数的参数来传递使用的。
给你一个实例:
要求:要设计一个CallMyFun函数,这个函数可以通过参数中的函数指针值不同来分别调用MyFun1、MyFun2、MyFun3这三个函数(注:这三个函数的定义格式应相同)。
实现:代码如下:

复制代码
#include <iostream>
using std::cout;
using std::endl;

void MyFun1(int x);
void MyFun2(int x);
void MyFun3(int x);
typedef void(*FunType)(int);        //2.定义一个函数指针FunType, 与1.函数类型一直 
void CallMyFun(FunType fp, int x);

int main()
{
    CallMyFun(MyFun1, 10);          //5.通过CallMyFun函数分别调用三个不同的函数 
    CallMyFun(MyFun2, 20);
    CallMyFun(MyFun3, 30);  
    
    system("pause");  
    return 0;
}

void CallMyFun(FunType fp, int x)   //3.参数fp的类型是Funtype 
{
     fp(x);                         //4.通过fp的指针执行传递进来的函数,注意fp所知的函数时有一个参数的 
}

void MyFun1(int x)
{
     cout << "MyFun1: " << x << endl;
}
void MyFun2(int x)
{
     cout << "MyFun1: " << x << endl;
}
void MyFun3(int x)
{
     cout << "MyFun1: " << x << endl;
}
复制代码

分析见注释。

参考:http://www.cnblogs.com/wuzhenbo/archive/2012/05/29/2524400.html

 

 

 

三、函数参数的传递问题(一级指针和二级指针)

程序1:

void myMalloc(char *s) //我想在函数中分配内存,再返回

{

  s=(char *) malloc(100);

}

void main()

{

  char *p=NULL;

  myMalloc(p); //这里的p实际还是NULL,p的值没有改变,为什么?

  if(p) free(p);

}

程序2:

void myMalloc(char **s)

{

  *s=(char *) malloc(100);

}

void main()

{

  char *p=NULL;

  myMalloc(&p); //这里的p可以得到正确的值了

  if(p) free(p);

}

程序3:

复制代码
#include <iostream>
using namespace std;

void fun(int *p)
{
     int b = 100;
     p = &b;
}

main()
{
      int a = 10;
      int *q;
      q = &a;
      printf("%d\n", *q);
      fun(q);
      printf("%d\n", *q);
      
      system("pause");
      return 0;
}
复制代码

结果:

图片内容为:

10

10

程序4:

复制代码
#include <iostream>
using namespace std;

void fun(int *p)
{
     *p = 100;
}

main()
{
      int a = 10;
      int *q;
      q = &a;
      printf("%d\n", *q);
      fun(q);
      printf("%d\n", *q);
      
      system("pause");
      return 0;
}
复制代码

结果为

图片内容为:

10

100

 

为什么?

---------------------------------------------------------------

1.被分配内存的是形参s,p没有分配内存;

2.被分配内存的是形参s指向的指针p,所以分配了内存。

---------------------------------------------------------------

 

不是指针没明白,是函数调用的问题!看看这段:

程序5指针参数是如何传递内存的?

     如果函数的参数是一个指针,不要指望用该指针去申请动态内存。程序5中,Test函数的语句GetMemory(str, 200)并没有使str获得期望的内存,str依旧是NULL,为什么?

复制代码
#include <iostream>
using namespace std;

void GetMemory1(char *p, int num)
{
     p = (char *)malloc(sizeof(char)*num);
}

int main()
{
    char *str = NULL;
    GetMemory1(str, 100);                  // str仍然为 NULL 
    strcpy(str, "hello")                  // 运行错误 
   system("pause"); return 0; 
}
复制代码

程序5 试图用指针参数申请动态内存

 

毛病出在函数GetMemory中。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,只是把_p所指的内存地址改变了,但是p丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会泄露一块内存,因为没有用free释放内存。

如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”,见程序6

复制代码
#include <iostream>
using namespace std;

void GetMemory2(char **p, int num)
{
     *p = (char *)malloc(sizeof(char)*num);
}

int main()
{
    char *str = NULL;
    GetMemory2(&str, 100);                  // 注意参数是&str,而不是str 
    strcpy(str, "hello");                  
    cout << str << endl;
    free(str); 
     
    system("pause");                     
    return 0;
}
复制代码

程序6用指向指针的指针申请动态内存

 

由于“指向指针的指针”这个概念不容易理解,我们可以用函数返回值来传递动态内存。这种方法更加简单,见示例7-4-3。

复制代码
#include <iostream>
using namespace std;

char *GetMemory3(int num)
{
     char *p = (char *)malloc(sizeof(char)*num);
     return p;
}

int main()
{
    char *str = NULL;
    str = GetMemory3(100);                  
    strcpy(str, "hello");                  
    cout << str << endl;
    free(str); 
     
    system("pause");                     
    return 0;
}
复制代码

程序7 用函数返回值来传递动态内存

 

用函数返回值来传递动态内存这种方法虽然好用,但是常常有人把return语句用错了。这里强调不要用return语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡,见程序8。

复制代码
#include <iostream>
using namespace std;

char *GetString(void)
{
     char p[] = "hello world";
     return p;  //编译器将提出警告 
}

int main()
{
    char *str = NULL;
    str = GetString();             //str 的内容是垃圾 
    cout << str << endl;
     
    system("pause");                     
    return 0;
}
复制代码

结果:

图片内容为:

Q=放假啊克雷格(乱码)

程序8 return语句返回指向“栈内存”的指针

 

用调试器逐步跟踪主函数,发现执行str = GetString语句后str不再是NULL指针,但是str的内容不是“hello world”而是垃圾。

如果把示例程序7改写成程序8,会怎么样?

复制代码
#include <iostream>
using namespace std;

char *GetString2(void)
{
     char *p = "hello world";
     return p;  //编译器将提出警告 
}

int main()
{
    char *str = NULL;
    str = GetString2();             //str 的内容是垃圾 
    cout << str << endl;
     
    system("pause");                     
    return 0;
}
复制代码

程序8 return语句返回常量字符串

 

函数程序8运行虽然不会出错,但是函数GetString2的设计概念却是错误的。因为GetString2内的“hello world”是常量字符串,位于静态存储区,它在程序生命期内恒定不变。无论什么时候调用GetString2,它返回的始终是同一个“只读”的内存块。

---------------------------------------------------------------

看看林锐的《高质量的C/C++编程》,上面讲得很清楚的

---------------------------------------------------------------

对于1和2:

如果传入的是一级指针S的话,

那么函数中将使用的是S的拷贝,

要改变S的值,只能传入指向S的指针,即二级指针

---------------------------------------------------------------

程序1:

void myMalloc(char *s) //我想在函数中分配内存,再返回

{

  s=(char *) malloc(100); // s是值参, 函数返回后就回复传递前的数值,无法带回分配的结果

}

这个和调用 void func (int i) {i=1;}; 一样,退出函数体,i指复原的

程序2:void myMalloc(char **s)

{

  *s=(char *) malloc(100); // 这个是可以的

}

等价于

void int func(int * pI) {*pI=1;} pI指针不变,指针指向的数据内容是变化的

值参本身不变,但是值参指向的内存的内容发生了变化。

程序3:

void fun(int *p)

{

  int b=100;

  p=&b;       // 等同于第一个问题, b的地址并没有被返回

}

程序4:

void fun(int *p)

{

  *p=100; // okay

}

结论:

1.       函数的返回值是指针类型的,检查是静态内存指针还是堆内存指针还是栈内存指针,栈内存指针是绝对要不得滴!

2.       函数需要使用指针参数进行传入传出的,在函数中只能对指针的指向的值(*p)进行修改,而不能修改指针指向,也就是指针地址!(函数中不得修改指针参数的地址,否则请使用指针的指针!)

参考:http://www.cnblogs.com/wuzhenbo/archive/2012/06/13/2547664.html

 

 

 

四、int* pInt = new int(5);和int* pInt = new int[5];

int *p = new int(5);
这句是从堆上分配一个int型变量所占的字节内存,这个内存单元存放的整数值为5,然后让一个整形的指针变量p指向它的地址。
释放方式:delete p;
int *p = new int[5];
这句相当于从堆上分配一个含有5个元素的整形数组所占的字节内存,然后让一个整形的指针变量p指向它的首址。
释放方式:delete []p;(注意这个[]不能掉了,如果掉了就会只释放P[0]所占的空间,p[1]到p[4]不会被释放,产生内存泄露。)

 

 

五、数组作为函数参数及作为函数模版参数的总结

今天编程序时发生了个这样的错误:

在头文件里 定义了一个数组:

View Code
1 char s[]="1234567890";

又定义了一个现显示组的函数:

View Code
1 void Display(char* c);

通过下面这两条语句分别在现实函数和主函数中现实数组的大小:

View Code
1     sizeof(c);
2     sizeof(s);

现实结果却大相径庭,在主函数中为11,在现实函数中却为4。

经过思考发现,在主函数中s代表的是一个数组,而在现实函数中c代表的是一个指向数组头的指针。数组的大小为11,指针的大小为4。

主程序如下:

View Code
复制代码
 1 #include<iostream>
 2 #include<stdlib.h>
 3 using namespace std;
 4 
 5 char s[]="1234567890";
 6 void Display(char* c);
 7 
 8 void main()
 9 {
10     char a;
11     cout<<"这个是在主函数中对数组长度的测试:"<<sizeof(s)<<endl;
12     Display(s);
13     cin>>a;
14 }
15 
16 
17 void Display(char* c)
18 {
19     cout<<"这个是在子函数中对数组长度的测试:"<<sizeof(c)<<endl;
20 }
复制代码

现实结果:

我的问题是怎样可以在主函数和子函数中都指的是数组而不是一个指向数组头的指针???

 

在网上查了几种将数组作为函数参数进行传递的方法,列举如下:

View Code
复制代码
 1 #include<iostream>
 2 #include<stdlib.h>
 3 #include<vector>
 4 using namespace std;
 5 
 6 char s[]="1234567890";
 7 int a[10]={0,1};
 8 int b[10]={0,1};
 9 void Display(char* c);
10 void PutArray1(int *p,int length1);
11 void PutArray2(int p[],int length1);
12 void PutArray3(int p[10]);
13 void PutArray4(int (&p)[10]);
14 void PutArray5(vector<int>verc);
15 
16 void main()
17 {
18     char q;
19     cout<<"这个是在主函数中对数组长度的测试:"<<sizeof(s)<<endl;
20     Display(s);
21     cout<<"*********************************************"<<endl;
22     PutArray1(a,10);
23     PutArray2(a,10);
24     PutArray3(a);
25     PutArray4(b);
26     cin>>q;
27 }
28 
29 
30 void Display(char* c)
31 {
32     cout<<"这个是在子函数中对数组长度的测试:"<<sizeof(c)<<endl;
33 }
34 void PutArray1(int *p,int length1)
35 {
36     int length2=sizeof(p);
37     cout<<"第一种方法的输出:"<<endl;
38     cout<<"第一种方法数组的长度为:"<<length2<<endl;
39     for(int i=0;i<length1;i++)
40     {
41         cout<<p[i];
42     }
43     cout<<endl;
44 }
45 void PutArray2(int p[],int length1)
46 {
47     int length2=sizeof(p);
48     cout<<"第二种方法的输出:"<<endl;
49     cout<<"第二种方法数组的长度为:"<<length2<<endl;
50         for(int i=0;i<length1;i++)
51         {
52             cout<<p[i];
53         }
54     cout<<endl;
55 }
56 void PutArray3(int p[10])
57 {
58     int length2=sizeof(p);
59     cout<<"第三种方法的输出:"<<endl;
60     cout<<"第三种方法数组的长度为:"<<length2<<endl;
61         for(int i=0;i<9;i++)
62         {
63             cout<<p[i];
64         }
65     cout<<endl;
66 }
67 void PutArray4(int (&p)[10])
68 {
69     int length2=sizeof(p);
70     cout<<"第四种方法的输出:"<<endl;
71     cout<<"第四种方法数组的长度为:"<<length2<<endl;
72         for(int i=0;i<9;i++)
73         {
74             cout<<p[i];
75         }
76     cout<<endl;
77 }
78 void PutArray5(vector<int>verc)
79 {
80     vector<int>::iterator begin_iter=verc.begin();
81     vector<int>::iterator end_iter=verc.end();
82     int size=verc.size();
83     cout<<"第五种方法的输出:"<<endl;
84     cout<<"第五种方法数组的长度为:"<<size<<endl;
85     cout<<"下面这种方法是采用向量遍历的方法遍历数组:"<<endl;
86     for(vector<int>::iterator iter=begin_iter;iter!=end_iter;iter++)
87     {
88         cout<<*iter;
89     }
90     cout<<endl;
91     cout<<"下面这种方法是采用普通遍历数组的方法遍历数组:"<<endl;
92     for(int i=0;i<size-1;i++)
93     {
94         cout<<verc[i];
95     }
96     cout<<endl;
97 }
复制代码

在这里,int *arr和int arr[]的含义相同,编译器自动将 int arr[]替换为int *arr,所以这也解释了上面在主函数和子函数中利用数组名求数组长度会得到不同结果的原因。这种情况只有在数组作为函数参数进行传递时才会发生(C++ Primer Plus,P192)。

其中第四种方法没有理解,疑问暂时留在这里吧。

另外虽然上面四种方法都可以正确地在子函数中传递数组作为参数,但是仍然不能满足博客刚开始的要求:在子函数中可以测的参数数组的长度。后来查看C++ Primer Plus发现书上已经明确指出没有实现这种想法的方法,数组的长度必须在函数中作为参数进行传递。


另外由于第五种方法需要重新定义向量模版,和主题不符,所以在主函数里并没有调用它。例程中给的程序如下所示:

View Code
1 vector<int> verc1(a,a+10);
2 vector<int> verc2(b,b+8);
3 PutArray5(verc1);
4 PutArray5(verc2);

 上面这五种调用数组的方法只是在传递数组的方式上不同,可以归纳为传递数组的一种方法,即:传递指向数组头的指针和数组的长度。另外一种传递数组的方法是将指向数组头的指针和指向数组尾的指针作为两个参数进行传递,函数定义如下:

View Code
1 int sum_arr(const int* begin,const int* end);

在学习这个知识点时是有一些感想的,虽说C++ Primer已经看了一遍,里面的知识点也认真地消化过,但是一到用的时候就使不上劲。看来应该在看书的同时多实践书上的代码,这样才能对代码的应用有更好的理解。

本篇收集自:http://www.cnblogs.com/tziyachi/archive/2012/02/11/2347073.html
我看了下,方法4在方法声明时就固定了数组的使用长度了,没有任何灵活性可言,方法5没看懂,有空再说把。不过发现一个终极解决方案如下:

我们知道,在C/C++中,向一个函数传递数组时,实际上传送的是这个数组的首地址,也即是一个指针类型。所以,在函数中,我们没有办法知道这个传递进来的数组的长度到底为多长,一个普遍的方法就是再增加一个参数,来记录传递进来的数组的长度。
不过在C++中,使用模板机制,可以很好地解决这个问题,如下面的代码所示:
  #include <iostream>
  using namespace std;
  template<class T, int N>
  void array(T (&param)[N])
  {
  cout << "N= " << N << endl;
  }
  int main()
  {
  int i[100];
  double d[20];
  char c[6];
  array(i);
  array(d);
  array(c);
  return 0;
  } 来源:

【补充个东西,函数模版的模版非类型参数,原始定义在书中是这样的:

template<class T,int size>

T max(T(&array)[size])

{

      //函数体

}

上面模版方法定义中,size是一个模版非类型参数,专门用来表示数组array的长度,需要在编译之前确定他的值。

】


 


 

【C++】使用 yaml-cpp 库操作 YAML 配置文件有利于我们灵活配置工程,解决大量重复劳动,也方便调试。YAML 是一种人类可读的数据序列化格式,它使用缩进和特定的符号来表示数据结构。在本文中,我们将详细介绍如何在 C++ 中使用yaml-cpp库来解析和生成 YAML 格式的数据。 阅读详情

相关推荐

C++ Yaml文件解析安装及使用 yaml-cpp

C++ Yaml文件解析安装及使用安装 yaml-cpp克隆官方库编译 yaml-cpp示例代码robot.cpprobot.yaml编译 robot.cpp运行结果难点分析与总结什么是 .a 与 .so 文件静态链接库(.a) 与动态链接库(.so)静态链接库动态链接库g++(gcc)编译选项参考文章 安装 yaml-cpp 克隆官方库 git clone https://github.co...

Fourier_Legend的博客 2万+

C++高级语法

一.面向对象 1.1 类 C++使用Struct.class 来定义一个类: struct的默认成员权限为public class的默认成员权限是private class Student{ private: string name; double score; public : Student(); virtual ~Student(); // 析构函数 double GetScor...

流水荡荡的博客 1010

C++ 网络库 cpp-netlib的简单使用

示例代码 (C++11, cpp-netlib版本:0.12.0) 需要链接库文件 target_link_libraries(app boost_system network-uri cppnetlib-server-parsers ) #include <network/uri.hpp> #include <nlohmann/json.hpp...

逗神大人的博客 6219

C++难点(一)——指针

指针和内存管理是C++编程中非常重要的概念,也是初学者容易遇到困惑和错误的地方。

qq_65441028的博客 494

【log4】c++日志工具之——log4cpp

1、log4cpp概述 Log4cpp是一个开源的C++类库,它提供了C++程序中使用日志和跟踪调试的功能,它的优点如下: 提供应用程序运行上下文,方便跟踪调试; 可扩展的、多种方式记录日志,包括命令行、文件、回卷文件、内存、syslog服务器、Win事件日志等; 可以动态控制日志记录级别,在效率和功能中进行调整; 所有配置可以通过配置文件进行动态调整; 多语言支持,包括Java(log4j),C++(log4cpp、log4cplus),C(log4c),python(...

我的笔记本 8053

C++ log4cpp的使用

C++ log4cpp使用安装需要了解的四个部分(基本的) log4cpp官网 这里整理了需要使用到的基本内容 安装 从官网下载最新的库文件 下载地址 下载的文件名格式为 log4cpp-x.y.z.tar.gz 以下内容均在Linux中完成 版本信息 Linux version 5.3.0-46-generic (buildd@lcy01-amd64-013) (gcc version 7.5...

Zzzz的博客 787

C++中的重难点看这一篇就够了

sizeof()是一个运算符,不是一个函数 看程序效率的快慢,可以直接看其汇编语言程序的多少 扩展名: c语言:c c++:cpp Java:先有类,再有方法 c++完全兼容c语法 getchar()等待键盘输入(如果敲回车,就会读取键盘输入) 函数重载(overload) c语言不支持函数重载 两个函数的函数名完全相同,但是函数参数类型,个数,顺序不同 返回值类型不同,不能构成重载 隐式转换(小转大)也可在函数参数类型中适用,但要注意隐式转换有可能产生二义性。 本质: ​采用了na.

m0_48530561的博客 3150

C/C++编程:令人印象深刻的高级技巧案例

C/C++中的指针运算与内存管理是其独特之处。通过合理地使用指针,可以直接操作内存地址,实现高效的数据处理。例如,利用指针进行数组操作、动态内存分配以及实现复杂的数据结构等。

笔者从事电信媒体开发多年,愿意将多年的开发经验分享给同行 1342

c++难点

C++ 的难点主要在于其复杂的语法和规则,以及如何高效地使用其提供的各种特性。理解这些难点并掌握相应的技巧,可以大大提高开发效率和代码质量。C++ 是一种功能强大但复杂的编程语言,许多开发者在学习和使用过程中会遇到一些难点。以下是一些常见的 C++ 难点及其解析。:由编译器自动分配释放,存放函数的参数值、局部变量的值等。原因在于这些函数的特性与虚函数的多态性不兼容。:提高了对象的转移效率,减少了不必要的拷贝。:由程序员分配释放,存放动态分配的内存。时,结构体中的成员默认是。时,类中的成员默认是。

ssshhhhhwddddddd的博客 866

使用Gradle编译C++代码并添加CPP插件

在开发C++项目时,使用Gradle作为构建工具可以提供便捷的构建和依赖管理。以上步骤完成后,你就可以使用Gradle来构建和管理你的C++项目了。通过添加CPP插件,Gradle会自动为你创建构建任务,并处理C++的依赖关系。这是一个简单的示例,帮助你开始使用CPP插件在Gradle中编译C++代码。注意:确保你的电脑上安装了正确的C++编译器,并且Gradle能够找到它。接下来,我们创建一个新的Gradle项目,并进行以下配置。部分定义了我们的主要组件,包括源代码的位置和导出头文件的位置。

ByteEchoX的博客 649

ECU-TEST 快速入门

ECU-TEST 是由位于德国德累斯顿的TraceTronic公司开发的一款用于嵌入式系统测试验证软件工具。自从2003年首次发布ECU-TEST ,该软件成为了汽车ECU开发的标准工具 ,同时也逐步成为了重型机械和工业自动化开发的标准工具 。该软件是起源自于一个控制单元的系统化测试的项目研究,从而为从德累斯顿工业大学脱离出来的TraceTronic公司奠定了基础。ECU-TEST旨在完成测试案例的规范化、评估、求解和归档。得益于该软件包含大量的自动化测试方法使得测试案例的所有必要功能如创建、求解和评估

wangying202的博客 1万+

Cpp入门基础

定义命名空间,需要使⽤到namespace关键字,后⾯跟命名空间的名字,然后接⼀对{}即可,{}中 即为命名空间的成员。命名空间中可以定义变量/函数/类型等。namespace本质是定义出⼀个域,这个域跟全局域各⾃独⽴,不同的域可以定义同名变量,所以下 ⾯的rand不在冲突了。C++中域有函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找⼀个变量/函数/ 类型出处(声明或定义)的逻辑,所有有了域隔离,名字冲突就解决了。

2401_86658278的博客 5100

LDA主题模型简介及Python实现

一、LDA主题模型简介 LDA主题模型主要用于推测文档的主题分布,可以将文档集中每篇文档的主题以概率分布的形式给出根据主题进行主题聚类或文本分类。 LDA主题模型不关心文档中单词的顺序,通常使用词袋特征(bag-of-word feature)来代表文档。词袋模型介绍可以参考这篇文章:文本向量化表示——词袋模型 - 知乎 了解LDA模型,我们需要先了解LDA的生成模型,LDA认为一篇文章是怎么形成的呢? LDA模型认为主题可以由一个词汇...

weixin_41168304的博客 20万+

C++ 在一个cpp文件中使用另一个cpp文件中定义的函数

C++ 在一个cpp文件中 使用另一个cpp文件中定义的函数建立一个console项目头文件cpp文件main.cpp 建立一个console项目 以dev为例,在【文件】【新建】【项目】,新建一个console项目。 头文件 添加新文件,输入说要使用函数的声明,保存为.h文件。 int add(int a,int b); cpp文件 定义在.h中声明的函数,保存为.cpp文件。 #include<iostream> #include"chen.h"//不要忘记这个 int a

m0_43456002的博客 9261

Multisim滞回比较器电路设计与仿真实战:从理论到应用

本文详细介绍了Multisim滞回比较器电路设计与仿真实战,从基础理论到实际应用全面解析。通过门铃原理类比解释滞回特性,提供2-5V阈值检测电路的设计步骤和Multisim仿真技巧,并延伸至温度控制系统等实际工程应用,帮助读者掌握电路设计与优化的核心方法。

assembly8low的博客 1081

离线语音识别模块LD3320实战:如何低成本给STM32设备加上‘耳朵’和‘嘴巴’?

本文详细介绍了如何为STM32设备集成LD3320离线语音识别模块,实现低成本语音交互方案。从硬件连接到STM32底层驱动实现,再到指令训练与词条优化技巧,完整解析了唤醒词检测与自定义指令识别的全过程。LD3320作为非特定人声识别芯片,无需网络连接,适用于智能家居、工业控制等场景,是嵌入式开发者的理想选择。

weixin_30906185的博客 555

c++使用amqp-cpp库连接RabbitMq

AMQP-CPP简介 c++连接RabbitMq的库目前不多,很多朋友直接使用Rabbitmq-c封闭了类,供c++使用,也是一种方法。 经过选型和使用,我在项目中使用了AMQP-CPP,本文主要介绍AMQP-CPP库的使用。 AMQP-CPP是用于与RabbitMq消息中间件通信的c++库。它能解析从RabbitMq服务发送来的数据,也可以生成发向RabbitMq的数据包。 该库使用分层架...

guotianqing的博客 1万+

PoP(Package on Package)封装技术探讨

PoP(Package on Package)封装技术摘要 PoP是一种3D封装技术,通过垂直堆叠已完成封装的器件(如SoC+DRAM)实现高密度集成,典型应用于智能手机等空间受限场景。其核心优势包括:节省主板面积(垂直堆叠)、优化高速信号完整性(短互连路径)、提升供应链灵活性(独立测试与选型)。PoP结构分为底封装(通常为Flip-Chip BGA的SoC)和顶封装(如FBGA存储芯片),通过焊球阵列互连,一次回流焊完成组装。

sinat_15150363的博客 366

[PHP-CPP]使用C++来扩展PHP的能力

一个使用C++来开发PHP扩展的开源项目 什么是PHP-CPP? 它是一个C++库,用来开发PHP的扩展.它提供了一个文档丰富且易于使用的类库,可以使用这些类库开发PHP的扩展.也就是说我们可以用C++来开发PHP的扩展了. 为什么要使用PHP-CPP? 使用这个C++库,可以非常享受的开发出一个PHP的扩展,使用它编写的代码简单易懂,并且能为你的软件带来很大的提升. 使用PHP-CPP的特性和原因 1.性能更快 使用C++编写的代码相比PHP的代码来说,非常非常快,在官网给出的一个冒

PHP武器库 1505

MicroCap声学仿真介绍(二)-创建声学仿真元件

声学库里包含孔、管、障板辐射器等器件,我们只需要按照仿真的需求,将声学库里的器件排列好,输入参数,用线连接起来即可。这里我们用D指代孔的直径,L指代孔的长度,N代表孔的数量。26.在弹出的界面中做如下设置:需要先在右面的选框里找到对应的项,然后右键Add Part,再之后就如下设置。3.在弹出的界面中,选择“Macro file(.mac)”,并点击右上角的“OK”下面就以声学仿真中常见的“孔”来做示例,跟大家一块演示一下宏程序的新建。10.在上图中“Value”中填入“R”,然后点击左边的“OK”按钮;

山无忧的博客 2197

EMB电子机械制动执行器五大核心部件选型解析

本文介绍了EMB电子机械制动执行器五大核心部件的选型方案:采用无刷直流电机作为驱动源,兼具高扭矩和快速响应特性;选用滚珠丝杠传动机构,实现高效旋转-直线转换;行星齿轮减速器满足紧凑空间内的增扭需求;QT450-QT550材质的钳体确保刚度要求;应变片式压力传感器实现夹紧力精确测量。各部件选型综合考虑了性能、可靠性和空间限制,为EMB系统设计提供了完整解决方案。

m0_59012156的博客 1733

C++ 在一个cpp文件中 使用另一个cpp文件中定义的函数

第一步: 需要给你想用的cpp建一个头文件 定义一个头文件 myFunctions.h 为防止多次定义 要写保护符 #pragma once 头文件中声明自己定义函数 void fuction(int a); 第二步: 新建一个.cpp文件 定义在头文件中声明的函数 void fuction(int a ){ a = 2; } 第三步: 在使用该函数的.cpp中 #include"my...

简简简简abc 1万+

在 SAP 里玩转 FTP:标准程序 RSFTP00x 全景解析与落地实践

摘要:SAP系统通过封装的标准FTP程序(RSFTP001-008)提供便捷的文件传输能力,适用于各类集成场景。关键点包括:1)依赖SAPFTP可执行程序和RFC目的地;2)使用RSFTP005进行前置检查并生成RFC配置;3)标准报表各司其职,如RSFTP002用于命令测试、RSFTP007/008提供参考实现;4)开发时组合FTP_CONNECT/COMMAND/DISCONNECT等函数模块。特别注意前台(SAPFTP)与后台(SAPFTPA)的环境差异,这是常见故障点。该方案通过标准化工具链显著降低

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 210

C/C++编程:log4cpp使用学习

简介 Log4cpp是一个开源的C++类库,它提供了在C++程序中使用日志和跟踪调试的功能。使用log4cpp,可以很便利地将日志或者跟踪调试信息写入字符流、内存字符串队列、文件、回滚文件、调试器、Windows日志、本地syslog和远程syslog服务器中。 Log4cpp有如下优点: 提供了可扩展的多种日志记录方式; 提供了NDC(嵌套诊断上下文),可用于多线程、多场景的跟踪调试; 提供了完整的日志动态优先级控制,可随时调整需要记录的日志优先级 可通过配置文件完成所有配置并动态加载; 性能优秀,内存

OceanStar的博客 4019

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

C++编程的八大核心难点解析

C++的难点主要源于其底层控制、灵活性和演进历史。这些难点并非不可克服——通过系统学习(如阅读《C++ Primer》)、实践项目和使用工具(如Valgrind检测内存错误),您可以逐步掌握。核心是理解原理而非死记硬背。如果您有具体场景(如游戏开发或嵌入式系统),我可以提供更针对性的建议!

864

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

上一篇: 适配器模式--设计模式
下一篇: 巧妙算法收集
HNFXS985
博客等级 码龄14年 219粉丝 150原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值