指针问题
信息来源:网络


译者序:

  这是一篇我所见过的关于指针的最好的入门级文章,它可使初学者在很短的时间内掌握复杂的指针操作。虽然,现在的JAVA、C#等语言已经取消了指针,但作为一个C++程序员,指针的直接操作内存,在数据操作方面有着速度快,节约内存等优点,仍是很多C++程序员的最爱。指针就像是一把良剑,就看你怎么去应用它!
  有关这篇文章的技术性问题你可以写信给我:webmaster@chinahai.com.同时我的另外两篇相关文章《模板Guide》和《STL Guide》也快完成,希望能对您有所帮助!Loading... ...

译者:万江(chinahai)


什么是指针?

  其实指针就像是其它变量一样,所不同的是一般的变量包含的是实际的真实的数据,而指针是一个指示器,它告诉程序在内存的哪块区域可以找到数据。这是一个非常重要的概念,有很多程序和算法都是围绕指针而设计的,如链表。


开始学习

  如何定义一个指针呢?就像你定义一个其它变量一样,只不过你要在指针名字前加上一个星号。我们来看一个例子:
  下面这个程序定义了两个指针,它们都是指向整型数据。


int* pNumberOne;
int* pNumberTwo;

  你注意到在两个变量名前的“p”前缀了吗?这是程序员通常在定义指针时的一个习惯,以提高便程序的阅读性,表示这是个指针。现在让我们来初始化这两个指针:
pNumberOne = &some_number;
pNumberTwo = &some_other_number;
  &号读作“什么的地址”,它表示返回的是变量在内存中的地址而不是变量本身的值。在这个例子中,pNumberOne 等于some_number的地址,所以现在pNumberOne指向some_number。 如果现在我们在程序中要用到some_number,我们就可以使用pNumberOne。


我们来学习一个例子:

  在这个例子中你将学到很多,如果你对指针的概念一点都不了解,我建议你多看几遍这个例子,指针是个很复杂的东西,但你会很快掌握它的。
  这个例子用以增强你对上面所介绍内容的了解。它是用C编写的(注:原英文版是用C写的代码,译者重新用C++改写写了所有代码,并在DEV C++ 和VC++中编译通过!)


#include <iostream.h>

void main()
{
// 声明变量:
int nNumber;
int *pPointer;


// 现在给它们赋值:
nNumber = 15;
pPointer = &nNumber;

//打印出变量nNumber的值:
cout<<"nNumber is equal to :"<< nNumber<<endl;

// 现在通过指针改变nNumber的值:
*pPointer = 25;

//证明nNumber已经被上面的程序改变
//重新打印出nNumber的值:
cout<<"nNumber is equal to :"<<nNumber<<endl;
}

  通读一下这个程序,编译并运行它,务必明白它是怎样工作的。如果你完成了,准备好,开始下一小节。


陷井!

  试一下,你能找出下面这段程序的错误吗?

#include <iostream.h>

int *pPointer;

void SomeFunction();
{
int nNumber;
nNumber = 25;


//让指针指向nNumber:
pPointer = &nNumber;
}

void main()
{
SomeFunction(); //为pPointer赋值

//为什么这里失败了?为什么没有得到25
cout<<"Value of *pPointer: "<<*pPointer<<endl;
}

  这段程序先调用了SomeFunction函数,创建了个叫nNumber的变量,接着让指针pPointer指向了它。可是问题出在哪儿呢?当函数结束后,nNumber被删掉了,因为这一个局部变量。局部变量在定义它的函数执行完后都会被系统自动删掉。也就是说当SomeFunction 函数返回主函数main()时,这个变量已经被删掉,但pPointer还指着变量曾经用过的但现在已不属于这个程序的区域。如果你还不明白,你可以再读读这个程序,注意它的局部变量和全局变量,这些概念都非常重要。
  但这个问题怎么解决呢?答案是动态分配技术。注意这在C和C++中是不同的。由于大多数程序员都是用C++,所以我用到的是C++中常用的称谓。


动态分配

  动态分配是指针的关键技术。它是用来在不必定义变量的情况下分配内存和让指针去指向它们。尽管这么说可能会让你迷惑,其实它真的很简单。下面的代码就是一个为一个整型数据分配内存的例子:
int *pNumber;
pNumber = new int;
  第一行声明一个指针pNumber。第二行为一个整型数据分配一个内存空间,并让pNumber指向这个新内存空间。下面是一个新例,这一次是用double双精型:
double *pDouble;
pDouble = new double;
  这种格式是一个规则,这样写你是不会错的。
  但动态分配又和前面的例子有什么不同呢?就是在函数返回或执行完毕时,你分配的这块内存区域是不会被删除的所以我们现在可以用动态分配重写上面的程序:
#include <iostream.h>

int *pPointer;

void SomeFunction()
{
// 让指针指向一个新的整型
pPointer = new int;
*pPointer = 25;
}

void main()
{
SomeFunction(); // 为pPointer赋值

cout<<"Value of *pPointer: "<<*pPointer<<endl;
}
  通读这个程序,编译并运行它,务必理解它是怎样工作的。当SomeFunction 调用时,它分配了一个内存,并让pPointer指向它。这一次,当函数返回时,新的内存区域被保留下来,所以pPointer始终指着有用的信息,这是因为了动态分配。但是你再仔细读读上面这个程序,虽然它得到了正确结果,可仍有一个严重的错误。


分配了内存,别忘了回收

  太复杂了,怎么会还有严重的错误!其实要改正并不难。问题是:你动态地分配了一个内存空间,可它绝不会被自动删除。也就是说,这块内存空间会一直存在,直到你告诉电脑你已经使用完了。可结果是,你并没有告诉电脑你已不再需要这块内存空间了,所以它会继续占据着内存空间造成浪费,甚至你的程序运行完毕,其它程序运行时它还存在。当这样的问题积累到一定程度,最终将导致系统崩溃。所以这是很重要的,在你用完它以后,请释放它的空间,如:
delete pPointer;
  这样就差不多了,你不得不小心。在这你终止了一个有效的指针(一个确实指向某个内存的指针)。
  下面的程序,它不会浪费任何的内存:

#include <iostream.h>

int *pPointer;

void SomeFunction()
{
// 让指针指向一个新的整型
pPointer = new int;
*pPointer = 25;
}

void main()
{
SomeFunction(); //为pPointer赋值
cout<<"Value of *pPointer: "<<*pPointer<<endl;

delete pPointer;
} 

  只有一行与前一个程序不同,但就是这最后一行十分地重要。如果你不删除它,你就会制造一起“内存漏洞”,而让内存逐渐地泄漏。
  (译者:假如在程序中调用了两次SomeFunction,你又该如何修改这个程序呢?请读者自己思考)

传递指针到函数

  传递指针到函数是非常有用的,也很容易掌握。如果我们写一个程序,让一个数加上5,看一看这个程序完整吗?:
#include <iostream.h>

void AddFive(int Number)
{
Number = Number + 5;
}

void main()
{
int nMyNumber = 18;

cout<<"My original number is "<<nMyNumber<<endl;
AddFive(nMyNumber);
cout<<"My new number is "<<nMyNumber<<endl;
//得到了结果23吗?问题出在哪儿?
}
  问题出在函数AddFive里用到的Number是变量nMyNumber的一个副本而传递给函数,而不是变量本身。因此, " Number = Number + 5" 这一行是把变量的副本加了5,而原始的变量在主函数main()里依然没变。试着运行这个程序,自己去体会一下。
  要解决这个问题,我们就要传递一个指针到函数,所以我们要修改一下函数让它能接受指针:把'void AddFive(int Number)' 改成 'void AddFive(int* Number)' 。下面就是改过的程序,注意函数调用时要用&号,以表示传递的是指针:
#include <iostream.h>
void AddFive(int* Number)
{
*Number = *Number + 5;
}

void main()
{
int nMyNumber = 18;

cout<<"My original number is "<<nMyNumber<<endl;
AddFive(&nMyNumber);
cout<<"My new number is "<<nMyNumber<<endl;
}


  试着自己去运行它,注意在函数AddFive的参数Number前加*号的重要性:它告诉编译器,我们是把指针所指的变量加5。而不并指针自己加5。

  最后,如果想让函数返回指针的话,你可以这么写:
int * MyFunction();
  在这句里,MyFunction返回一个指向整型的指针。


指向类的指针

  指针在类中的操作要格外小心,你可以用如下的办法定义一个类:
class MyClass
{
  public:
  int m_Number;
  char m_Character;
};
  接着你就可以定义一个MyClass 类的变量了:
MyClass thing;
  你应该已经知道怎样去定义一个指针了吧:
MyClass *thing;
  接着你可以分配个内存空间给它:
thing = new MyClass;
  注意,问题出现了。你打算怎样使用这个指针呢,通常你可能会写'thing.m_Number',但是thing是类吗,不,它是一个指向类的指针,它本身并不包含一个叫m_Number的变量。所以我们必须用另一种方法:就是把'.'(点号)换成 -> ,来看下面的例子:
class MyClass
{
public:
int m_Number;
char m_Character;
};

void main()
{
MyClass *pPointer;
pPointer = new MyClass;

pPointer->m_Number = 10;
pPointer->m_Character = 's';

delete pPointer;
}

指向数组的指针

  你也可以让指针指向一个数组,按下面的方法操作:
int *pArray;
pArray = new int[6];
  程序会创建一个指针pArray,让它指向一个有六个元素的数组。另外一种方法,不用动态分配:
int *pArray;
int MyArray[6];
pArray = &MyArray[0];
  注意,&MyArray[0] 也可以简写成 MyArray ,都表示是数组的第一个元素地址。但如果写成pArray = &MyArray可能就会出问题,结果是 pArray 指向的是指向数组的指针(在一维数组中尽管与&MyArray[0]相等),而不是你想要的,在多维数组中很容易出错。


在数组中使用指针

  一旦你定义了一个指向数组的指针,你该怎样使用它呢?让我们来看一个例子,一个指向整型数组的指针:

#include <iostream.h>

void main()
{
int Array[3];
Array[0] = 10;
Array[1] = 20;
Array[2] = 30;

int *pArray;
pArray = &Array[0];

cout<<"pArray points to the value %d\n"<<*pArray<<endl;
}

  如果让指针指向数组元素中的下一个,可以用pArray++.也可以用你应该能想到的pArray + 1,都会让指针指向数组的下一个元素。要注意的是你在移动指针时,程序并不检查你是否已经移动地超出了你定义的数组,也就是说你很可能通过上面的简单指针加操作而访问到数组以外的数据,而结果就是,可能会使系统崩溃,所以请格外小心。
  当然有了pArray + 1,也可以有pArray - 1,这种操作在循环中很常用,特别是while循环中。
  另一个需要注意的是,如果你定义了一个指向整型数的指针:int* pNumberSet ,你可以把它当作是数组,如:pNumberSet[0] 和 *pNumberSet是相等的,pNumberSet[1]与*(pNumberSet + 1)也是相等的。
  在这一节的最后提一个警告:如果你用 new 动态地分配了一个数组,
int *pArray;
pArray = new int[6];
  别忘了回收,
delete[] pArray;
  这一句是告诉编译器是删除整个数组而不一个单独的元素。千万记住了。


后话

  还有一点要小心,别删除一个根本就没分配内存的指针,典型的是如果没用new分配,就别用delete:

void main()
{
  int number;
  int *pNumber = number;

  delete pNumber; // 错误 - *pNumber 没有用new动态分配内存.
}

常见问题解答

Q:为什么我在编译程序时老是在 new 和 delete语句中出现'symbol undefined' 错误?
A:new 和 delete都是C++在C上的扩展,这个错误是说编译器认为你现在的程序是C而不C++,当然会出错了。看看你的文件名是不是.cpp结尾。

Q:new 和 malloc有什么不同?
A:new 是C++中的关健字,用来分配内存的一个标准函数。如果没有必要,请不要在C++中使用malloc。因为malloc是C中的语法,它不是为面向对象的C++而设计的。

Q:我可以同时使用free 和 delete吗?
A:你应该注意的是,它们各自所匹配的操作不同。free只用在用malloc分配的内存操作中,而delete只用在用new分配的内存操作中。


引用(写给某些有能力的读者)

  这一节的内容不是我的这篇文章的中心,只是供某些有能力的读者参考。
  有些读者经常问我关于引用和指针的问题,这里我简要地讨论一下。
  在前面指针的学习中,我们知道(&)是读作“什么的地址”,但在下面的程序中,它是读作“什么的引用”

int& Number = myOtherNumber;
Number = 25;
  引用有点像是一个指向myOtherNumber的指针,不同的是它是自动删除的。所以他比指针在某些场合更有用。与上面等价的代码是:
int* pNumber = &myOtherNumber;
*pNumber = 25;
  指针与引用另一个不同是你不能修改你已经定义好的引用,也就是说你不能改变它在声明时所指的内容。举个例子:
int myFirstNumber = 25;
int mySecondNumber = 20;
int &myReference = myFirstNumber;

myReference = mySecondNumber;//这一步能使myReference 改变吗?

cout<<myFristNumber<<endl;//结果是20还是25?

  当在类中操作时,引用的值必须在构造函数中设定,例:

CMyClass::CMyClass(int &variable) : m_MyReferenceInCMyClass(variable)
{
  // constructor code here
}


总结

  这篇文章开始可能会较难掌握,所以最好是多读几遍。有些读者暂时还不能理解,在这儿我再做一个简要的总结:
  指针是一个指向内存区域的变量,定义时在变量名前加上星号(*)(如:int *number)。
  你可以得到任何一个变量的地址,只在变量名前加上&(如:pNumber = &my_number)。
  你可以用'new' 关键字动态分配内存。指针的类型必须与它所指的变量类型一样(如:int *number 就不能指向 MyClass)。
  你可以传递一个指针到函数。必须用'delete'删除你动态分配的内存。
  你可以用&array[0]而让指针指向一个数组。
  你必须用delete[]而不是delete来删除动态分配的数组。

  文章到这儿就差不多结束了,但这些并不就是指针所有的东西,像指向指针的指针等我还没有介绍,因为这些东西对于一个初学指针的人来说还太复杂了,我不能让读者一开始就被太复杂的东西而吓走了。好了,到这儿吧,试着运行我上面写的小程序,也多自己写写程序,你肯定会进步不小的!
posted @ 2010-06-21 22:32 lhking 阅读(357) | 评论 (0)编辑 收藏
  1. int *a = new a;   
  2. int *b = new b();  

 

其中a 为 随机值,按书上说法是,a为上次位于该内存的值,b初始化为0;现测试类类型的结果

也就是说, 对于没有提供任何构造函数的类,new 的时候加括号,会帮你创造一个默认构造函数,而且帮你初始化;

而new的时候没有加括号,也会帮你构造一个默认构造函数,只是什么都没做。

posted @ 2010-05-28 11:19 lhking 阅读(108) | 评论 (0)编辑 收藏
请问个位高手小弟看c++教程有一点没明白
new一个对象必须delete删除,不是new的对象就不需要删除了吗?

代码:
Person p("john green"); 
cout < < p.getName();    //string name的属性值还存在,此时还占着内存

delete &p;        //只有

cout < < p.getName();  //这样才提示不存在,如果delete &p注释掉,p对象的name属性始终是占着内存的


但是我看很多教程上的代码不是new的对象他们并没有delete,
难道不是new的对象就不用delete,但是他自己并不回收内存阿()????????


解析:
1、p只是一个栈变量,不是堆变量,会自动析构的,不能使用delete

2、你这个问题让我感觉无可奈何。

感觉缺少基本的理解。


生存在栈上的对象和生存在堆上的对象你要搞清楚。

堆需要手动回收。

栈则由系统自动回收!
3、不是new的,在退出作用域后会自动析构。其占用的内存也会被自动回收掉。
这种自动变量才是最方便的,也不会造成内存泄露,因此,能用的时候尽量用,不要在自动变量完全可以解决问题的时候却硬是要采用堆上动态申请的对象。
posted @ 2010-05-28 11:06 lhking 阅读(845) | 评论 (0)编辑 收藏

这个问题在许多C++程序员看来会很搞笑,“这么简单的问题还用得着你废话!”。但是由于本人生性愚钝,学C++的时候,确实花了很久的时间都搞不明白,只是会写一个文件,但不会写一个工程。而用C++编写比较大型的项目时,文件的分割管理确实确实是非常必要的 。下面就非常简洁明了地谈谈头文件(.h)和源文件(.cpp)应该怎么写。

   头文件(.h):
    写类的声明(包括类里面的成员和方法的声明)、函数原型、#define常数等,但一般来说不写出具体的实现。

    在写头文件时需要注意,在开头和结尾处必须按照如下样式加上预编译语句(如下):

#ifndef CIRCLE_H
#define CIRCLE_H

//你的代码写在这里

#endif
    这样做是为了防止重复编译,不这样做就有可能出错。

    至于CIRCLE_H这个名字实际上是无所谓的,你叫什么都行,只要符合规范都行。原则上来说,非常建议把它写成这种形式,因为比较容易和头文件的名字对应。

   源文件(.cpp):

    源文件主要写实现头文件中已经声明的那些函数的具体代码。需要注意的是,开头必须#include一下实现的头文件,以及要用到的头文件。那么当你需要用到自己写的头文件中的类时,只需要#include进来就行了。

    下面举个最简单的例子来描述一下,咱就求个圆面积。

     第1步,建立一个空工程(以在VS2003环境下为例)。

     第2步,在头文件的文件夹里新建一个名为Circle.h的头文件,它的内容如下:

#ifndef CIRCLE_H
#define CIRCLE_H

class Circle
...{
private:
    double r;//半径
public:
    Circle();//构造函数
    Circle(double R);//构造函数
    double Area();//求面积函数
};

#endif
   注意到开头结尾的预编译语句。在头文件里,并不写出函数的具体实现。

    第3步,要给出Circle类的具体实现,因此,在源文件夹里新建一个Circle.cpp的文件,它的内容如下:

#include "Circle.h"

Circle::Circle()
...{
    this->r=5.0;
}

Circle::Circle(double R)
...{
    this->r=R;
}

double Circle:: Area()
...{
    return 3.14*r*r;
}
    需要注意的是:开头处包含了Circle.h,事实上,只要此cpp文件用到的文件,都要包含进来!这个文件的名字其实不一定要叫Circle.cpp,但非常建议cpp文件与头文件相对应。

    最后,我们建一个main.cpp来测试我们写的Circle类,它的内容如下:

#include <iostream>
#include "Circle.h"
using namespace std;

int main()
...{
    Circle c(3);
    cout<<"Area="<<c.Area()<<endl;
    return 1;
}
    注意到开头时有#include "Circle.h"的声明,证明我们使用到了我们刚才写的Circle类。

   至此,我们工程的结构为:

 

    运行一下,输出结果为:

 

   说明我们写的Circle类确实可以用了。

posted @ 2010-05-28 10:34 lhking 阅读(24964) | 评论 (1)编辑 收藏

 

#include <iostream>
#include 
<fstream>
#include 
<string>

using namespace std;

int main()
{
    
const char filename[] = "mytext.txt";
    ofstream o_file;
    ifstream i_file;
    
string out_text;

    
//
    o_file.open(filename);
    
for (int i = 1; i <= 10; i++)
    
{
        o_file 
<< "" << i << ""<<endl; //将内容写入到文本文件中
    }

    o_file.close();

    
//
    i_file.open(filename);
    
if (i_file.is_open())
    
{
        
while (i_file.good())
        
{
            i_file
>> out_text; //将读取的内容存储到变量out_text中
            cout << out_text << endl; //在控制台输出读取的内容。为什么最后一行的内容会出现两次
        }

    }

    
else
        cout 
<< "打开文件:" << filename << " 时出错!";
    i_file.close();

    system(
"PAUSE");
    
return 0;
}



preheader:
#include "stdafx.h"
#include 
<iostream>
#include 
<fstream>
#include 
<string>
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    
const char filename[]="test.doc";
    ofstream o_file;
/* 输出流:将数据从内存输出其中ofstream是将数据输出到文件,因此对于文件来说是“写”*/
    ifstream i_file;
/*将数据输入到内存,其中ifstream是说输入的数据在文件中,因此对于文件来说是“读”*/
    
string out_text;

    
//
    o_file.open(filename);
    
for(int i =0;i<=12;i++)
    
{
        o_file
<<""<<i<<"行\n";//将内容写入文本
    }

    o_file.close();
    
//
    i_file.open(filename);
    
if(i_file.is_open())
    
{
        
while(i_file>>out_text)
        
{
            cout 
<< out_text << endl;
        }

    }

    
else
        cout
<<"打开文件:"<<filename<<"时出错!";
    i_file.close();
    system(
"PAUSE");

    
return 0;
}
 
posted @ 2010-05-28 09:46 lhking 阅读(241) | 评论 (0)编辑 收藏

作用域运算符::是用来标识某个成员函数是属于哪个类的。

在C++中,有一个stream这个类,所有的I/O都以这个“流”类为基础的,包括我们要认识的文件I/O,stream这个类有两个重要的运算符:

1、插入器(<<)
  向流输出数据。比如说系统有一个默认的标准输出流(cout),一般情况下就是指的显示器,所以,cout<<"Write Stdout"<<' ';就表示把字符串"Write Stdout"和换行字符(' ')输出到标准输出流。

2、析取器(>>)
  从流中输入数据。比如说系统有一个默认的标准输入流(cin),一般情况下就是指的键盘,所以,cin>>x;就表示从标准输入流中读取一个指定类型(即变量x的类型)的数据。

  在C++中,对文件的操作是通过stream的子类fstream(file stream)来实现的,所以,要用这种方式操作文件,就必须加入头文件fstream.h。


A.cpp要引用B.cpp的函数。
最干净的方法是,为B.cpp建立一个头文件B.h,将A.cpp要调用的函数声明写进去。
然后在A.cpp里#include这个B.h。

很C的方法是。在A.cpp里面直接extern 那个B.cpp里的函数,就是在extern后面写上函数声明。然后B.cpp里的那个函数要是static的。


附加说明
使用命名空间
2008要求较严格 .h是C语言里的东西,所以包含头文件时应该用
#include <iostream>
using namespace std;


c++中的STL和MFC
c++是一门编程语言,这门语言有它自己的标准和规范(比如有自己的语法)。
同样,针对C++这门语言,标准化组织又规定了相关的“程序库”,程序库中有各式各样的工具(都是由高手编写的,所以可用性极佳)供编程人员使用,而STL(standard   template   library,标准模板库)就是C++“程序库”的一部分。
至于MFC,它只不过是“利用C++的语法对windows   API进行的面向对象的封装”而已,也就是说,有了mfc,我们不用“直接”调用windows   API,而是可以通过C++的语法、以面向对象的方式使用windows   API。

C++   是一种编程语言,可以支持成面向过程,或者基于过程,或者面向对象设计方法
STL   是C++标准程序库,提供处了基本的数据类型int   char,还有扩展的用类实现的容器类型如vector,list等标准泛型容器类型,以及一些通用的泛型算法
MFC,一个framework,   用面向对象的方法,来封装win32   api来进行windows   平台上的程序开发

这是因为在函数结束之后要释放临时变量。对指针赋值,一定要采用“指针的指针”。 
我改过的程序如下: 

void   init(int   **base,int   **top) 

int   *temp   =   NULL; 
temp   
=   (int*)   malloc(sizeof(int)*50); 
*base   =   temp; 
*top     =   temp; 
}
 

void   main() 

int   *base   =   NULL,   *top   =   NULL; 
init(
&base,   &top); 
printf( 
"%X\t%X\n ",   base,   top); 
}
 
最后不要忘了调用free()去释放你分配的内存。

vs2008下cannot convert parameter 1 from 'LPCTSTR' to 'const char *'的解决方法:
1. 使用_T()实现ASCII与UNICODE自动转换
2. 在使用字符串的使用 T"Hello world"或者L"hello world"
3. 设置Character Set为Use Multi-Byte Character Set
posted @ 2010-05-28 09:46 lhking 阅读(270) | 评论 (0)编辑 收藏
今天注册c++博客了
posted @ 2010-05-28 08:42 lhking 阅读(362) | 评论 (0)编辑 收藏
仅列出标题
共3页: 1 2 3 

导航

<2024年12月>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

统计

常用链接

留言簿

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜