zoukankan      html  css  js  c++  java
  • C++学习——指针详解大全,带你揭开层层迷雾

    最近因工作要求要来学习C++了,在这里做一下学习的记录

    对于C语言而言,最重要的就是指针了,它是C语言的特色也是难点之一,当然,作为继承者C++也是如此

    指针

    int * p_updates;

    这表明,* p_updates的类型为int。由于*运算符被用于指针,因此p_updates变量本身必须是指针。我们说p_updates指向int类型,我们还说p_updates的类型是指向int的指针,或int*。可以这样说,p_updates是指针(地址),而*p_updates是int,而不是指针.

     指针的危险

    危险更易发生在那些使用指针不仔细的人身上。极其重要的一点是:在C++中创建指针时,计算机将分配用来存储地址的内存,但不会分配用来存储指针所指向的数据的内存。为数据提供空间是一个独立的步骤,忽略这一步无疑是自找麻烦.

    long * fellow;
    *fellow = 223323;

    fellow确实是一个指针,但它指向哪里呢?上述代码没有将地址赋给fellow。那么223323将被放在哪里呢?我们不知道。由于fellow没有被初始化,它可能有任何值。不管值是什么,程序都将它解释为存储223323的地址。如果fellow的值碰巧为1200,计算机将把数据放在地址1200上,即使这恰巧是程序代码的地址。fellow指向的地方很可能并不是所要存储223323的地方。这种错误可能会导致一些最隐匿、最难以跟踪的bug。

    指针和数字

    指针不是整型,虽然计算机通常把地址当作整数来处理。从概念上看,指针与整数是截然不同的类型。整数是可以执行加、减、除等运算的数字,而指针描述的是位置,将两个地址相乘没有任何意义。从可以对整数和指针执行的操作上看,它们也是彼此不同的。因此,不能简单地将整数赋给指针:

    int *pt;
    pt = 0xB8000000; //type mismatch

    在这里,左边是指向int的指针,因此可以把它赋给地址,但右边是一个整数。您可能知道,0xB8000000是老式计算机系统中视频内存的组合段偏移地址,但这条语句并没有告诉程序,这个数字就是一个地址。在C99标准发布之前,C语言允许这样赋值。但C++在类型一致方面的要求更严格,编译器将显示一条错误消息,通告类型不匹配。要将数字值作为地址来使用,应通过强制类型转换将数字转换为适当的地址类型:

    int *pt;
    pt = (int *)0xB8000000;

    这样,赋值语句的两边都是整数的地址,因此这样赋值有效。注意,pt是int值的地址并不意味着pt本身的类型是int。例如,在有些平台中,int类型是个2字节值,而地址是个4字节值。

    使用new来分配内存

    对指针的工作方式有一定了解后,来看看它如何实现在程序运行时分配内存。前面我们都将指针初始化为变量的地址;变量是在编译时分配的有名称的内存,而指针只是为可以通过名称直接访问的内存提供了一个别名。指针真正的用武之地在于,在运行阶段分配未命名的内存以存储值。在这种情况下,只能通过指针来访问内存。在C语言中,可以用库函数malloc( )来分配内存;在C++中仍然可以这样做,但C++还有更好的方法—new运算符。

    下面来试试这种新技术,在运行阶段为一个int值分配未命名的内存,并使用指针来访问这个值。这里的关键所在是C++的new运算符。程序员要告诉new,需要为哪种数据类型分配内存;new将找到一个长度正确的内存块,并返回该内存块的地址。程序员的责任是将该地址赋给一个指针。下面是一个这样的示例:

    int * pn = new int;

    new int告诉程序,需要适合存储int的内存。new运算符根据类型来确定需要多少字节的内存。然后,它找到这样的内存,并返回其地址。接下来,将地址赋给pn,pn是被声明为指向int的指针。现在,pn是地址,而*pn是存储在那里的值。将这种方法与将变量的地址赋给指针进行比较:

    int higgens;
    int *pt = &higgens;

    在这两种情况(pn和pt)下,都是将一个int变量的地址赋给了指针。在第二种情况下,可以通过名称higgens来访问该int,在第一种情况下,则只能通过该指针进行访问。这引出了一个问题:pn指向的内存没有名称,如何称呼它呢?我们说pn指向一个数据对象,这里的“对象”不是“面向对象编程”中的对象,而是一样“东西”。术语“数据对象”比“变量”更通用,它指的是为数据项分配的内存块。因此,变量也是数据对象,但pn指向的内存不是变量。乍一看,处理数据对象的指针方法可能不太好用,但它使程序在管理内存方面有更大的控制权。

    为一个数据对象(可以是结构,也可以是基本类型)获得并指定分配内存的通用格式如下:

    typeName * pointer_name = new typeName;(如: double * p = new double)

    使用delete释放内存

    当需要内存时,可以使用new来请求,这只是C++内存管理数据包中有魅力的一个方面。另一个方面是delete运算符,它使得在使用完内存后,能够将其归还给内存池,这是通向最有效地使用内存的关键一步。归还或释放(free)的内存可供程序的其他部分使用。使用delete时,后面要加上指向内存块的指针(这些内存块最初是用new分配的):

    int *ps = new int;
    delete ps;

    这将释放ps指向的内存,但不会删除指针ps本身。例如,可以将ps重新指向另一个新分配的内存块。一定要配对地使用new和delete;否则将发生内存泄漏(memory leak),也就是说,被分配的内存再也无法使用了。如果内存泄漏严重,则程序将由于不断寻找更多内存而终止。

    不要尝试释放已经释放的内存块,C++标准指出,这样做的结果将是不确定的,这意味着什么情况都可能发生。

    另外,不能使用delete来释放声明变量所获得的内存,如下:

    int jugs = 5;
    int *pi = &jugs;
    delete pi; //错误..内存不是通过new来进行分配的

    意思就是 new delete是要同对出现的。

    使用delete的关键在于,将它用于new分配的内存。这并不意味着要用于new的指针,而是用于new的地址

    int *ps = new int;
    int *pq = ps;
    delete pq;

    一般来说,不要创建两个指向同一个内存块的指针,因为这将增加错误地删除同一个内存块两次的可能性。但稍后您会看到,对于返回指针的函数,使用另一个指针确实有道理。

    使用new来创建动态数组

    如果程序只需要一个值,则可能会声明一个简单变量,因为对于管理一个小型数据对象来说,这样做比使用new和指针更简单,尽管给人留下的印象不那么深刻。通常,对于大型数据(如数组、字符串和结构),应使用new,这正是new的用武之地。例如,假设要编写一个程序,它是否需要数组取决于运行时用户提供的信息。如果通过声明来创建数组,则在程序被编译时将为它分配内存空间。不管程序最终是否使用数组,数组都在那里,它占用了内存。在编译时给数组分配内存被称为静态联编(static binding),意味着数组是在编译时加入到程序中的。但使用new时,如果在运行阶段需要数组,则创建它;如果不需要,则不创建。还可以在程序运行时选择数组的长度。这被称为动态联编(dynamic binding),意味着数组是在程序运行时创建的。这种数组叫作动态数组(dynamic array)。使用静态联编时,必须在编写程序时指定数组的长度;使用动态联编时,程序将在运行时确定数组的长度。

    在C++中,创建动态数组很容易;只要将数组的元素类型和元素数目告诉new即可。必须在类型名后加上方括号,其中包含元素数目。例如,要创建一个包含10个int元素的数组,可以这样做:

    int *psome = new int[10];

    new运算符返回第一个元素的地址。在这个例子中,该地址被赋给指针psome。

    当程序使用完new分配的内存块时,应使用delete释放它们。然而,对于使用new创建的数组,应使用另一种格式的delete来释放:

    delete [] psome;

    方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素。请注意delete和指针之间的方括号。如果使用new时,不带方括号,则使用delete时,也不应带方括号。如果使用new时带方括号,则使用delete时也应带方括号。C++的早期版本无法识别方括号表示法。然而,对于ANSI/ISO标准来说,new与delete的格式不匹配导致的后果是不确定的,这意味着程序员不能依赖于某种特定的行为。下面是一个例子:

    int * pt = new int;
    short * ps = new short[500];
    delete [] pt;   //effect is undefined, don't do this
    delete ps;      //effect is undefined, don't do this

    总之,使用new和delete时,应遵守以下规则。

    • 不要使用delete来释放不是new分配的内存。
    • 不要使用delete释放同一个内存块两次。
    • 如果使用new [ ]为数组分配内存,则应使用delete [ ]来释放。
    • 如果使用new [ ]为一个实体分配内存,则应使用delete(没有方括号)来释放。
    • 对空指针应用delete是安全的。

    下面的语句创建指针psome,它指向包含10个int值的内存块中的第1个元素:

    int * p = new int[10];

    可以将它看作是一根指向该元素的手指。假设int占4个字节,则将手指沿正确的方向移动4个字节,手指将指向第2个元素。总共有10个元素,这就是手指的移动范围。因此,new语句提供了识别内存块中每个元素所需的全部信息。

    现在从实际角度考虑这个问题。如何访问其中的元素呢?第一个元素不成问题。由于psome指向数组的第1个元素,因此*psome是第1个元素的值。这样,还有9个元素。如果没有使用过C语言,下面这种最简单的方法可能会令您大吃一惊:只要把指针当作数组名使用即可。也就是说,对于第1个元素,可以使用psome[0],而不是*psome;对于第2个元素,可以使用psome[1],依此类推。这样,使用指针来访问动态数组就非常简单了,虽然还不知道为何这种方法管用。可以这样做的原因是,C和C++内部都使用指针来处理数组。数组和指针基本等价是C和C++的优点之一(这在有时候也是个问题,但这是另一码事)。稍后将更详细地介绍这种等同性

    int main() {
        double* p3 = new double[3];
        p3[0] = 0.2;
        p3[1] = 0.5;
        p3[2] = 0.8;
        cout << "p3[1] is " << p3[1] << endl;
        p3 = p3 + 1;
        cout << "now p3[0] is " << p3[0] << endl;
        cout << "p3[1] is " << p3[1] << endl;
        p3 = p3 - 1;
        delete[] p3;
        return 0;
    }

    下面是该程序的输出:

    p3[1] is 0.5
    now p3[0] is 0.5
    p3[1] is 0.8

    从中可知,指针p3当作数组名来使用,p3[0]为第1个元素,依次类推。下面的代码行指出了数组名和指针之间的根本差别:

     p3 = p3+1; 

    不能修改数组名的值。但指针是变量,因此可以修改它的值。请注意将p3加1的效果。表达式p3[0]现在指的是数组的第2个值。因此,将p3加1导致它指向第2个元素而不是第1个。将它减1后,指针将指向原来的值,这样程序便可以给delete[ ]提供正确的地址。

    相邻的int地址通常相差2个字节或4个字节,而将p3加1后,它将指向下一个元素的地址,这表明指针算术有一些特别的地方。情况确实如此。

    指针、数组和指针算术

    指针和数组基本等价的原因在于指针算术(pointer arithmetic)和C++内部处理数组的方式。首先,我们来看一看算术。将整数变量加1后,其值将增加1;但将指针变量加1后,增加的量等于它指向的类型的字节数。将指向double的指针加1后,如果系统对double使用8个字节存储,则数值将增加8;将指向short的指针加1后,如果系统对short使用2个字节存储,则指针值将增加2。以下程序演示了这种令人吃惊的现象,它还说明了另一点:C++将数组名解释为地址

    int main() {
        double wages[3] = { 10000.0,20000.0,30000.0 };
        short stacks[3] = { 3,2,1 };
        //获取数组地址的两种方式
        double* pw = wages; //数组名即为地址
        short* ps = &stacks[0];
        cout << "pw= " << pw << ", *pw = " << *pw << endl;
        pw = pw + 1;
        cout << "pw指针加1:
    ";
        cout << "pw= " << pw << ", *pw = " << *pw << endl;
        cout << endl;
    
        cout << "ps= " << ps << ", *ps = " << *ps << endl;
        ps = ps + 1;
        cout << "ps指针加1:
    ";
        cout << "ps= " << ps << ", *ps = " << *ps << endl;
        cout << endl;
    
        cout << "使用数组表示法访问两个元素" << endl;
        cout << "stack[0] = " << stacks[0] << ", stack[1] = " << stacks[1] << endl;
        cout << "使用指针表示法访问两个元素" << endl;
        cout << "*stack = " << *stacks << ", *(stack+1) = " << *(stacks+1) << endl;
    
        cout << "wages数组的大小为:" << sizeof(wages) << endl;
        cout << "pw指针的大小为:" << sizeof(pw) << endl;
    }

    下面是该程序的输出:

    pw= 00EFFE80, *pw = 10000
    pw指针加1:
    pw= 00EFFE88, *pw = 20000
    
    ps= 00EFFE70, *ps = 3
    ps指针加1:
    ps= 00EFFE72, *ps = 2
    
    使用数组表示法访问两个元素
    stack[0] = 3, stack[1] = 2
    使用指针表示法访问两个元素
    *stack = 3, *(stack+1) = 2
    wages数组的大小为:24
    pw指针的大小为:4

    在多数情况下,C++将数组名解释为数组第1个元素的地址。因此,下面的语句将pw声明为指向double类型的指针,然后将它初始化为wages—wages数组中第1个元素的地址:

    double * pw =wages;
    //和所有数组一样,wages也存在下面的等式
    wages = &wages[0] = 数组第一个元素的地址

    为表明情况确实如此,该程序在表达式&stacks[0]中显式地使用地址运算符来将ps指针初始化为stacks数组的第1个元素。

    接下来,程序查看pw和*pw的值。前者是地址,后者是存储在该地址中的值。由于pw指向第1个元素,因此*pw显示的值为第1个元素的值,即10000。接着,程序将pw加1。正如前面指出的,这样数字地址值将增加8,这使得pw的值为第2个元素的地址。因此,*pw现在的值是20000—第2个元素的值(参见下图,为使改图更为清晰,对其中的地址值做了调整)。

     此后,程序对ps执行相同的操作。这一次由于ps指向的是shor t类型,而short占用2个字节,因此将指针加1时,其值将增加2。结果是,指针也指向数组中下一个元素。

    现在来看一看数组表达式stacks[1]。C++编译器将该表达式看作是*(stacks + 1),这意味着先计算数组第2个元素的地址,然后找到存储在那里的值。最后的结果便是stacks [1]的含义(运算符优先级要求使用括号,如果不使用括号,将给*stacks加1,而不是给stacks加1)。

    很多情况下,可以相同的方式使用指针名和数组名。对于它们,可以使用数组方括号表示法,也可以使用解除引用运算符(*)。在多数表达式中,它们都表示地址。区别之一是,可以修改指针的值,而数组名是常量。

    数组的地址

    对数组取地址时,数组名也不会被解释为其地址。等等,数组名难道不被解释为数组的地址吗?不完全如此:数组名被解释为其第一个元素的地址,而对数组名应用地址运算符时,得到的是整个数组的地址:

        short tell[10];
        cout << tell << endl; //显示 &tell[0];
        cout << &tell << endl; //显示整个数组的地址

    从数字上说,这两个地址相同;但从概念上说,&tell[0](即tell)是一个2字节内存块的地址,而&tell是一个20字节内存块的地址。因此,表达式tell + 1将地址值加2,而表达式&tell + 2将地址加20。换句话说,tell是一个short指针(* short),而&tell是一个这样的指针,即指向包含20个元素的short数组(short (*) [20])。

    您可能会问,前面有关&tell的类型描述是如何来的呢?首先,您可以这样声明和初始化这种指针:

     short (*pas)[20] = &tell 

    如果省略括号,优先级规则将使得pas先与[20]结合,导致pas是一个short指针数组,它包含20个元素,因此括号是必不可少的。其次,如果要描述变量的类型,可将声明中的变量名删除。因此,pas的类型为short (*) [20]。另外,由于pas被设置为&tell,因此*pas与tell等价,所以(*pas) [0]为tell数组的第一个元素。

    总之,使用new来创建数组以及使用指针来访问不同的元素很简单。只要把指针当作数组名对待即可。然而,要理解为何可以这样做,将是一种挑战。要想真正了解数组和指针,应认真复习它们的相互关系。

    总结

    1.声明指针

    要声明指向特定类型的指针,请使用下面的格式:

     typeName * pointerName //实例 double * pn; char *pc; 

    其中,pn和pc都是指针,而double *和char *是指向double的指针和指向char的指针。

    2.给指针赋值

    应将内存地址赋给指针。可以对变量名应用&运算符,来获得被命名的内存的地址,new运算符返回未命名的内存的地址。

    double *pn;
    double *pa;
    char *pc;
    double bubble = 3.2;
    pn = &bubble;
    pc = new char;
    pa = new double[30];

    3.对指针解除引用

    对指针解除引用意味着获得指针指向的值。对指针应用解除引用或间接值运算符(*)来解除引用。因此,如果像上面的例子中那样,pn是指向bubble的指针,则*pn是指向的值,即3.2。

     cout<< *pn; *pc='S' 

    另一种对指针解除引用的方法是使用数组表示法,例如,pn[0]与*pn是一样的。决不要对未被初始化为适当地址的指针解除引用。

    4.区分指针和指针所指向的值

    如果pt是指向int的指针,则*pt不是指向int的指针,而是完全等同于一个int类型的变量。pt才是指针。

     int *pt = new int; *pt =5; 

    5.数组名

    在多数情况下,C++将数组名视为数组的第一个元素的地址。

     int tacos[10]; 

    一种例外情况是,将sizeof运算符用于数组名用时,此时将返回整个数组的长度(单位为字节)。

    6.指针算术

    C++允许将指针和整数相加。加1的结果等于原来的地址值加上指向的对象占用的总字节数。还可以将一个指针减去另一个指针,获得两个指针的差。后一种运算将得到一个整数,仅当两个指针指向同一个数组(也可以指向超出结尾的一个位置)时,这种运算才有意义;这将得到两个元素的间隔

    int main() {
        int tacos[10] = {5,2,8,4,1,2,2,4,6,8}; 
        int* pt = tacos;            //假设tacos的起始地址为3000
        pt = pt + 1;                //如果int占4字节,此时就是pt = 3004
        int* pe = &tacos[9];        //pe为3036
        pe = pe - 1;                //pe为3032
        int diff = pe - pt;         //diff = 7,tacos[8]与tacos[1]的间隔
        cout << pt << endl;
        cout << pe << endl;
    
        cout << diff << endl;
    }

    7.数组的动态联编和静态联编

    使用数组声明来创建数组时,将采用静态联编,即数组的长度在编译时设置:

     int tacos[10]; 

    使用new[ ]运算符创建数组时,将采用动态联编(动态数组),即将在运行时为数组分配空间,其长度也将在运行时设置。使用完这种数组后,应使用delete [ ]释放其占用的内存:

    int * pz = new int[10];
    delete [] pz;

    8.数组表示法和指针表示法

    使用方括号数组表示法等同于对指针解除引用:

      tacos[0]=*tacos=tacos地址的值 

      tacos[3]=*(tacos+3)=tacos+3地址的值 

    数组名和指针变量都是如此,因此对于指针和数组名,既可以使用指针表示法,也可以使用数组表示法。

    拓展部分

     指针和字符串

    数组和指针的特殊关系可以扩展到C-风格字符串。请看下面的代码:

        char flower[10] = "rose";
        cout << flower << endl;

    输出结果:

    rose

    数组名是第一个元素的地址,因此cout语句中的flower是包含字符r的char元素的地址。cout对象认为char的地址是字符串的地址,因此它打印该地址处的字符,然后继续打印后面的字符,直到遇到空字符()为止。总之,如果给cout提供一个字符的地址,则它将从该字符开始打印,直到遇到空字符为止

    这里的关键不在于flower是数组名,而在于flower是一个char的地址。这意味着可以将指向char的指针变量作为cout的参数,因为它也是char的地址。当然,该指针指向字符串的开头,稍后将核实这一点。

    前面的cout语句中最后一部分的情况如何呢?如果flower是字符串第一个字符的地址,则表达式“s are red ”是什么呢?为了与cout对字符串输出的处理保持一致,这个用引号括起的字符串也应当是一个地址。在C++中,用引号括起的字符串像数组名一样,也是第一个元素的地址。上述代码不会将整个字符串发送给cout,而只是发送该字符串的地址。这意味着对于数组中的字符串、用引号括起的字符串常量以及指针所描述的字符串,处理的方式是一样的,都将传递它们的地址。与逐个传递字符串中的所有字符相比,这样做的工作量确实要少。

    以下例子演示了如何使用不同形式的字符串。它使用了两个字符串库中的函数。函数strlen( )我们以前用过,它返回字符串的长度。函数strcpy( )将字符串从一个位置复制到另一个位置。这两个函数的原型都位于头文件cstring(在不太新的实现中,为string.h)中。该程序还通过注释指出了应尽量避免的错误使用指针的方式。

    int main() {
        char animal[20] = "bear";
        const char* bird = "wren";
        char* ps;
    
        cout << animal << "and";
        cout << bird << endl;
    
        cout << "Enter a kind of animal:";
        cin >> animal;
    
        ps = animal;
        cout << ps << "!
    ";
        cout << "Before using strcpy():" << endl;
        cout << animal << " at " << (int*)animal << endl;
        cout << ps << " at " << (int*)ps << endl;
    
        ps = new char[strlen(animal) + 1];
        strcpy(ps, animal);
        cout << "After using strcpy():" << endl;
        cout << animal << " at " << (int*)animal << endl;
        cout << ps << " at " << (int*)ps << endl;
        delete[] ps;
        return 0;
    }

    下面是程序运行情况:

    程序分析:

     程序创建了一个char数组(animal)和两个指向char的指针变量(bird和ps)。该程序首先将animal数组初始化为字符串“bear”,就像初始化数组一样。然后,程序执行了一些新的操作,将char指针初始化为指向一个字符串:

     const char* bird = "wren"; 

    记住,“wren”实际表示的是字符串的地址,因此这条语句将“wren”的地址赋给了bird指针。(一般来说,编译器在内存留出一些空间,以存储程序源代码中所有用引号括起的字符串,并将每个被存储的字符串与其地址关联起来。)这意味着可以像使用字符串“wren”那样使用指针bird,如下面的示例所示:

     cout<<"A concerned"<<bird<<"speaks"; 

    字符串字面值是常量,这就是为什么代码在声明中使用关键字const的原因。以这种方式使用const意味着可以用bird来访问字符串,但不能修改它。最后,指针ps未被初始化,因此不指向任何字符串(正如您知道的,这通常是个坏主意,这里也不例外)

    接下来,程序说明了这样一点,即对于cout来说,使用数组名animal和指针bird是一样的。毕竟,它们都是字符串的地址,cout将显示存储在这两个地址上的两个字符串(“bear”和“wren”)。如果激活错误地显示ps的代码,则将可能显示一个空行、一堆乱码,或者程序将崩溃。创建未初始化的指针有点像签发空头支票:无法控制它将被如何使用。

    对于输入,情况有点不同。只要输入比较短,能够被存储在数组中,则使用数组animal进行输入将是安全的。然而,使用bird来进行输入并不合适:

    • 有些编译器将字符串字面值视为只读常量,如果试图修改它们,将导致运行阶段错误。在C++中,字符串字面值都将被视为常量,但并不是所有的编译器都对以前的行为做了这样的修改。
    • 有些编译器只使用字符串字面值的一个副本来表示程序中所有的该字面值。

    下面讨论一下第二点。C++不能保证字符串字面值被唯一地存储。也就是说,如果在程序中多次使用了字符串字面值“wren”,则编译器将可能存储该字符串的多个副本,也可能只存储一个副本。如果是后面一种情况,则将bird设置为指向一个“wren”,将使它只是指向该字符串的唯一一个副本。将值读入一个字符串可能会影响被认为是独立的、位于其他地方的字符串。无论如何,由于bird指针被声明为const,因此编译器将禁止改变bird指向的位置中的内容。

    试图将信息读入ps指向的位置将更糟。由于ps没有被初始化,因此并不知道信息将被存储在哪里,这甚至可能改写内存中的信息。幸运的是,要避免这种问题很容易—只要使用足够大的char数组来接收输入即可。请不要使用字符串常量或未被初始化的指针来接收输入。为避免这些问题,也可以使用std::string对象,而不是数组。

    接下来,请注意下述代码完成的工作:

        ps = animal;
        //...
        cout << animal << " at " << (int *)animal << endl;
        cout << ps << " at " << (int*)ps << endl;        

    它将生成下面的输出:

    fox at 0049FAA4
    fox at 0049FAA4

    一般来说,如果给cout提供一个指针,它将打印地址。但如果指针的类型为char *,则cout将显示指向的字符串。如果要显示的是字符串的地址,则必须将这种指针强制转换为另一种指针类型,如int *(上面的代码就是这样做的)。因此,ps显示为字符串“fox”,而(int *)ps显示为该字符串的地址。注意,将animal赋给ps并不会复制字符串,而只是复制地址。这样,这两个指针将指向相同的内存单元和字符串

    要获得字符串的副本,还需要做其他工作。首先,需要分配内存来存储该字符串,这可以通过声明另一个数组或使用new来完成。后一种方法使得能够根据字符串的长度来指定所需的空间:

     ps = new char[strlen(animal) + 1]; 

    字符串“fox”不能填满整个animal数组,因此这样做浪费了空间。上述代码使用strlen( )来确定字符串的长度,并将它加1来获得包含空字符时该字符串的长度。随后,程序使用new来分配刚好足够存储该字符串的空间。

    接下来,需要将animal数组中的字符串复制到新分配的空间中。将animal赋给ps是不可行的,因为这样只能修改存储在ps中的地址,从而失去程序访问新分配内存的唯一途径。需要使用库函数strcpy( ):

     strcpy(ps, animal); 

    strcpy( )函数接受2个参数。第一个是目标地址,第二个是要复制的字符串的地址。您应确定,分配了目标空间,并有足够的空间来存储副本。在这里,我们用strlen( )来确定所需的空间,并使用new获得可用的内存。

    通过使用strcpy( )和new,将获得“fox”的两个独立副本:

    fox at 0049FAA4
    fox at 004301c8
  • 相关阅读:
    用FileSystemWatcher监视文件系统
    生成随机汉字验证码
    MySQL学习笔记二
    python高级学习笔记
    boost bind 表达式中的是值语义还是指针语义?
    容器与适配器的个人总结
    subversion linux使用方法
    boost asio(初学示例)
    MySQL学习笔记一
    subversion 命令
  • 原文地址:https://www.cnblogs.com/Qiansion/p/11499008.html
Copyright © 2011-2022 走看看