• C++必看


    c++ 必看  

     

    1 在整型对象中存储浮点数时,浮点数将通过截短转换为整数值,例如

      int x=0;

      x=23.6;

      执行后,x的值将是23

    2 iostream对象cin是使用提取操作符>>的输入流。提取操作符可以从输入流中提取数值。

    3 C中,每一个字符串的结尾加一个字符串结束标志,以便系统判断字符串是否结束。’\0’ 为字符串结束标志,’\0’ 是一个ASCII码为0的字符,从ASCII代码表中可以看到ASCII码为0的字符是空操作字符,即它不引起任何控制动作,也不是一个可显示的字符。’\0’是由系统自动添加的。

    4 strlen的结果未统计’\0’所占用的1个字节。Sizeof的结果统计’\0’所占的1个字符

    5 变量或函数声明时,后缀的声明运算符比前缀的声明运算符约束力更强,比如*kings [] 就是一个指向某个东西的指针数组

    6 一个名字的作用域从它开始被声明的那点开始

    Int x;

    Void f()

    {

      Int x=x //用x自己初始化自己,此处是局部的x,不是全局x

    }

     

    函数参数被当作在函数最外层的块中声明的名字,所以

    Void f(int x) // 此处的x属于该函数块中的局部变量

    {

      Int x; //错误 重复定义

    }

    7 如果没有为变量提供初始式,全局的、名字空间的、局部静态的对象(静态对象)将被自动初始化为适当类型的0,局部对象(自动对象)和在自由存储区里建立的 对象(有时称为动态对象或堆对象)将不会有默认的初始值,可能是随机的,数组和结构成员,则要根据数组或结构是否为静态来确定是否默认的进行初始化。

    8 在 一个函数里声明的对象都在其定义被遇到时建立,在它的名字离开作用域时销毁,这种对象叫做自动对象。在全局和名字空间里的对象,以及在函数和类里声明为 static的对象只建立一次,它们一直生存到程序结束,这种对象称为静态对象。数组成员、非静态结构和类的成员的生存期由它们作为其部分的对象决定。通 过new和delete运算符,可以建立生存期可以直接控制的对象。

    9 char* const cp //到char的const指针

    const char* pc //到const char的指针

    有人发现从右向左读这种定义很有帮助,如”cp是一个const指针到char”

    10 Struct和Class类型对象的大小未必是其成员的大小之和,这是因为许多机器上,对象被称为具有”对齐”性质。对齐会在结构或类中造成”空洞”。

    11 虚基类说明格式如下
       virtual <继承方式><基类名>
      其中,virtual是虚类的关键字。虚基类的说明是用在定义派生类时,写在派生类名的后面。例如:

    class A
    {

     public:

    void f();

    protected:

      int a;

     };

    class B : virtual public A
    {
     protected:
      int b;
     };
    class C : virtual public A
    {
    protected:
      int c:
     };
    class D : public B, public C
    {
    public:
      int g();
      private:
      int d;
    };

    12 建立一个对象时会调用其构造函数,而建立一个对象的指针不会调用构造函数。

    13 派生类对象可以赋值给基类对象,基类对象不能赋值给派生类对象;

    派生类对象指针可以赋值给基类对象指针;基类对象指针不能赋值给派生类对象指针

    14 带有纯虚函数的类称为抽象类

    纯虚函数 virtual 函数类型函数名(参数表)=0

    15 要想设置状态栏最左边的文本,可以用

    m_wndStatusBar.SetWindowText()

    或者

    m_wndStatusBar.SetPaneText()

    16 const char cch='1';

    char ch='2';

     

    const char* p=&cch;

    const char* const p=&cch;

    char* const p=&cch; //wrong

    char* p=&cch; //wrong

    const char* p=&ch;

    const char* const p=&ch;

    char* const p=&ch;

    char* p=&ch;

    16 静态局部变量属于静态存储类别,在静态存储区分配存储单元,在程序整个运行期间不释放。静态局部变量是编译时赋初值的,只赋初值一次,在程序运行时已有初值。如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值为0(对数值型变量)或空字符(对字符变量)。而对自动变量来说,则是一个不确定的值。

    17 define是在预编译时处理的,它只能做简单的字符串替换,而typedef时在编译时处理的,它并不是简单的字符串替换,而是采用如同定义变量的方法那样来声明一个类型。

    18 数组指针 : 一个指向一维或者多维数组的指针;
    int * b=new int[10]; 指向一维数组的指针b ;
    注 意,这个时候释放空间一定要delete [] ,否则会造成内存泄露, b 就成为了空悬指针. int (*b2)[10]=new int[10][10]; 注意,这里的b2指向了一个二维int型数组的首地址.注意:在这里,b2等效于二维数组名,但没有指出其边界,即最高维的元素数量,但是它的最低维数的 元素数量必须要指定!就像指向字符的指针,即等效一个字符串,不要把指向字符的指针说成指向字符串的指针。这与数组的嵌套定义相一致。

    int(*b3) [30] [20]; //三级指针――>指向三维数组的指针; //注意倒序

    b3=new int [1] [20] [30];

    int (*b2) [20]; //二级指针;

    b2=new int [30] [20];
    两个数组都是由600个整数组成,前者是只有一个元素的三维数组,每个元素为30行20列的二维数组,而另一个是有30个元素的二维数组,每个元素为20个元素的一维数组。

    删除这两个动态数组可用下式:
    delete [] b3; //删除(释放)三维数组;
    delete [] b2; //删除(释放)二维数组;
    再次重申:这里的b2的类型是int (*) ,这样表示一个指向二维数组的指针。
    b3表示一个指向(指向二维数组的指针)的指针,也就是三级指针.

    19众所周知,在实现多态的过程中,一般将基类的析构函数设为virtual,以便在delete的时候能够多态的链式调用。那么析构函数是否可以设为纯虚呢?
    class CBase
    {
    public:
    CBase()
    {

    printf("CBase()\n");
    }
    virtual ~CBase() = 0;
    };
    答案是可以,那么这样实现的目的是什么呢?当然是避免实例化。
    但因为派生类不可能来实现基类的析构函数,所以基类析构函数虽然可以标为纯虚,但是仍必须实现析构函数,否则派生类无法继承,也无法编译通过。

    20 如果将头文件包含在< >中,预处理器就会先对由/I 编译指令指定的路径进行查找,然后再在由INCLUDE环境便领中定义的路径进行查找;如果将头文件包含在“”中,预处理器就会首先在包含有#include指令的文件相同的目录中查找,然后是/I 选项指定的路径,最后是INCLUDE环境变量中定义的路径进行查找。

    21探索C++的秘密之详解extern "C"

    时常在cpp的代码之中看到这样的代码:
    #ifdef __cplusplus
    extern "C" {
    #endif
    //一段代码
    #ifdef __cplusplus
    }
    #endif
      这样的代码到底是什么意思呢?首先,__cpluspluscpp中的自定义宏,那么定义了这个宏的话表示这是一段cpp的代码,也就是说,上面的代码的含义是:如果这是一段cpp的代码,那么加入extern "C"{}处理其中的代码。

      要明白为何使用extern "C",还得从cpp中对函数的重载处理开始说起。在c++中,为了支持重载机制,在编译生成的汇编码中,要对函数的名字进行一些处理,加入比如函数的返回类型等等.而在C中,只是简单的函数名字而已,不会加入其他的信息.也就是说:C++C对产生的函数名字的处理是不一样的.

      比如下面的一段简单的函数,我们看看加入和不加入extern "C"产生的汇编代码都有哪些变化:
    int f(void)
    {
    return 1;
    }
      在加入extern "C"的时候产生的汇编代码是:
    .file "test.cxx"
    .text
    .align 2
    .globl _f
    .def _f; .scl 2; .type 32; .endef
    _f:
    pushl %ebp
    movl %esp, %ebp
    movl $1, %eax
    popl %ebp
    ret
      但是不加入了extern "C"之后
    .file "test.cxx"
    .text
    .align 2
    .globl __Z1fv
    .def __Z1fv; .scl 2; .type 32; .endef
    __Z1fv:
    pushl %ebp
    movl %esp, %ebp
    movl $1, %eax
    popl %ebp
    ret
      两段汇编代码同样都是使用gcc -S命令产生的,所有的地方都是一样的,唯独是产生的函数名,一个是_f,一个是__Z1fv

      明白了加入与不加入extern "C"之后对函数名称产生的影响,我们继续我们的讨论:为什么需要使用extern "C"呢?C++之父在设计C++之时,考虑到当时已经存在了大量的C代码,为了支持原来的C代码和已经写好C库,需要在C++中尽可能的支持C,而extern "C"就是其中的一个策略。

      试想这样的情况:一个库文件已经用C写好了而且运行得很良好,这个时候我们需要使用这个库文件,但是我们需要使用C++来写这个新的代码。如果这个代码使用的是C++的方式链接这个C库文件的话,那么就会出现链接错误.我们来看一段代码:首先,我们使用C的处理方式来写一个函数,也就是说假设这个函数当时是用C写成的:
    //f1.c
    extern "C"
    {
    void f1()
    {
    return;
    }
    }
      编译命令是:gcc -c f1.c -o f1.o 产生了一个叫f1.o的库文件。再写一段代码调用这个f1函数:
    // test.cxx
    //这个extern表示f1函数在别的地方定义,这样可以通过
    //编译,但是链接的时候还是需要
    //链接上原来的库文件.
    extern void f1();
    int main()
    {
    f1();

    return 0;
    }
      通过gcc -c test.cxx -o test.o 产生一个叫test.o的文件。然后,我们使用gcc test.o f1.o来链接两个文件,可是出错了,错误的提示是:
    test.o(.text + 0x1f):test.cxx: undefine reference to 'f1()'
      也就是说,在编译test.cxx的时候编译器是使用C++的方式来处理f1()函数的,但是实际上链接的库文件却是用C的方式来处理函数的,所以就会出现链接过不去的错误:因为链接器找不到函数。
      因此,为了在C++代码中调用用C写成的库文件,就需要用extern "C"来告诉编译器:这是一个用C写成的库文件,请用C的方式来链接它们。
      比如,现在我们有了一个C库文件,它的头文件是f.h,产生的lib文件是f.lib,那么我们如果要在C++中使用这个库文件,我们需要这样写:
    extern "C"
    {
    #include "f.h"
    }
      回到上面的问题,如果要改正链接错误,我们需要这样子改写test.cxx:
    extern "C"
    {
    extern void f1();
    }
    int main()
    {
    f1();
    return 0;
    }
      重新编译并且链接就可以过去了.
      总结
      CC++对函数的处理方式是不同的.extern "C"是使C++能够调用C写作的库文件的一个手段,如果要对编译器提示使用C的方式来处理函数的话,那么就要使用extern "C"来说明。

    22 指针和引用的区别:

    1 指针在运行时可以改变其所指向的值,而引用一旦和某个对象绑定后就不再改变

    2 指针可以指向NULL,而引用必须初始化为指向一个对象

    3 程序为指针变量分配内存区域,而引用不分配内存区域

    23 常引用所引用的对象不能被更新;静态数据成员在类外初始化;常数据成员只能通过初始化列表来获得初值;

    24 如果将一个对象说明为常对象,则通过该对象只能调用它的常成员函数,而不能调用其他成员函数;const关键字可以用于参与对重载函数的区分,例如:

    void print();

    void print() const;

    这是对print()的有效重载。

    25 参数类型或参数个数的不同称作函数重载;只有返回值不同不能称作函数重载

    26 const对象只能访问const成员函数,一般对象既可以访问const成员函数,也可以访问非const成员函数;const对象可以访问非const成员变量,但该对象的非const成员变量值不能改变。

    27 宏和函数的区别:1. 宏做的是简单的字符串替换(注意是字符串的替换,不是其他类型参数的替换),而函数的参数的传递,参数是有数据类型的,可以是各种各样的类型.2. 宏的参数替换是不经计算而直接处理的,而函数调用是将实参的值传递给形参,既然说是值,自然是计算得来的.3. 宏在编译之前进行,即先用宏体替换宏名,然后再编译的,而函数显然是编译之后,在执行时,才调用的.因此,宏占用的是编译的时间,而函数占用的是执行时的 时间.4. 宏的参数是不占内存空间的,因为只是做字符串的替换,而函数调用时的参数传递则是具体变量之间的信息传递,形参作为函数的局部变量,显然是占用内存的. 5. 函数的调用是需要付出一定的时空开销的,因为系统在调用函数时,要保留现场,然后转入被调用函数去执行,调用完,再返回主调函数,此时再恢复现场,这些操 作,显然在宏中是没有的.宏不能访问类的私有成员。

    现在来看内联函数:所谓"内联函数"就是将很简单的函数"内嵌"到调用他的程序代码中,只样做的目的是为了避免上面说到的第5点,目的旨在节约下原本函数调用时的时空开销.但必须注意的是:作为内联函数,函数体必须十分简单,不能含有循环、条件、选择等复杂的结构

    28 任何类内部定义(注意不是声明)的成员函数都是内联函数。对于类体外定义的成员函数,可以在成员函数返回类型前加上关键字inline来将其定义为内联函数。

    29 静态变量存储在静态数据区中,而不是堆中。

    30 全局变量也是存储在静态数据区。

    31 在局部类(函数内定义的类,不包括嵌套类中定义的类)中不允许有静态成员。

    32 因为静态成员函数没有this指针,所以它不能访问非静态的数据成员,也不能调用非静态的成员函数,这些函数要用到this指针)

    33 转换连接指定

    如果C++中编写一个程序需要用到C库,那该怎么办呢?如果这样声明一个C函数:

    float f(int a,char b);

    C++的编译器就会将这个名字变成像_f_int_int之类的东西以支持函数重载(和类型安全连接)。然而,C编译器编译的库一般不做这样的转换,所以它 的

    内部名为_f。这样,连接器将无法解决我们C++对f()的调用。

    C+ +中提供了一个连接转换指定,它是通过重载extern关键字来实现的。extern后跟一个字符串来指定我们想声明的函数的连接类型,后面是函数声明。 extern "C" float f(int a,char b);这就告诉编译器f ( )是C连接,这样就不会转换函数名。标准的连接类型指定符有“C”和“C++”两种,但编译器开发商可选择用同样的方法支持其他语言。如果我们有一组转换 连接的声明,可以把它们放在花括号内:

    extern “C”

    {

    float(int a,char b);

    double d(int c,int d);

    }

    或:

    extern “C”

    {

    #include “myheader.h”

    }

    34 文件操作时,在文本形式下,在向计算机输入文本时,将回车换行符转换为一个换行符,在输出时把换行符转换为回车和换行两个字符。在用二进制文件时,不进行这种转换,在内存中的数据形式与输出倒外部文件中的数据形式完全一致,一一对应。

    35 在向PC机里加载程序时,操作系统就会为程序代码分配足够的内存,同时还要提供数据所需的空间――数据存储区。在数据段声明的变量和对象,称为全局的,他 们的初始值常常为0或程序定义的任何值。大多数操作系统还会分配一个称为heap(堆)的内存区域。堆里的对象通常不进行初始化,程序在使用这些对象之前 进行赋值。为了保存寄存器信息,操作系统还会分配一个栈。临时变量会保存在栈里。

    36 在C++中,结构可以包含函数,包括构造函数和析构函数,而在C中,结构不能包含函数,尽管它可以包含一个外部函数的指针。

    37 C++中,结构可以继承类。

    38 注意sizeof的参数可以是表达式,在这种情况下只考虑表达式的类型而不会计算表达式的值,例如

    int x=3;

    sizeof(++x)

    执行完后x仍等于3,并不执行++操作。

    39 对于字符串常量来说,#define特别低效,如果使用#define定义了一个常量字符串,以后每次使用这个串,都要重新为它划分一片内存空间。对于要 使用多次的字符串来说,会浪费很大的内存空间。一个const常量只需要分配一次空间,它实际上是一个不可改变的变量。使用它时,只是调出了它在初始化时 存储起来的值。

    40 #define定义字符串常量时不允许换行,如果字符串太长,需要多行时,可以:每行字符串都用“”包含,并在除最后一行的每一行末尾加\,如:

    #define NETWORK "A network can be loosely defined"\

    "as the hardware and software that"\

    "allow two entities to communicate";

    41 volidate是const的反义词,volidate告诉编译器,变量的值可能会在任何时候发生改变,甚至可能会在外界的影响下改变,一种情况是一个线程改变的另一个线程的变量。

    42 左值(lvalue)必须是一个可修改的对象。函数、常量、引用变量和预处理定义都不能作为左值使用,因为它们是不可修改的。引用函数,即返回值是一个数 据对象的引用的函数,可以当作左值使用,提供一个可修改的引用对象。在这种情况下,修改是针对引用对象进行的,而不是针对函数本身。

    43 对C++来说,内存被分为4种存储区域:

    代码区:存放程序代码

    数据区:用来存放全局变量、静态变量和常量

    栈区:用来存放局部变量、函数参数、返回值、返回地址和临时变量等

    堆区:用来存放其余数据,包括堆对象等

    44 如果一个类种没有定义拷贝构造函数,则系统自动生成一个默认拷贝构造函数。该默认拷贝构造函数的功能是将已知对象的所有数据成员的值拷贝给未知对象的所有对应的数据成员。

    45 指向成员变量的指针:

    格式如下:

    <类型说明符> <类名>:: *<指针名>

    例如,有类A:

    Class A

    {

    Public:

    int fun(int b){return a*c+b;}

    A(int i){a=i;}

    int c;

    private:

    int a;

    }

    定义一个指向指向类A的成员变量c的指针pc,格式如下:

    int A::*pc=&A::c;

    使用如下:

    A a;

    a.*pc=7;

    46 指向成员函数的指针

    格式如下:

    <类型说明符> (<类名>:: *指针名)(<参数表>)

    下面定义一个指向类A中成员函数fun的指针pfun,格式如下

    int (A::*pfun)(int)=A::fun;

    调用:

    A a;

    (a.*pfun)(3);

    47 友元类是把一个类当作另一个类的友元,当一个类作为另一个类的友元时,友元类中的所有成员函数都是另一个类的友元函数。

    48 构造函数的类型转换功能

    如果类定义中提供了单参数的构造函数,使用单参数的构造函数可以将某种数据类型的数值或变量转换为该类的对象。这便是单参数构造函数所具有的类型转换功能。

    #include <iostream.h>

    class D

    {

    public:

    D(){d=0;}

    D(double i){d=i;}

    private:

    double d;

    };

     

    void main()

    {

    D d1;

    d1=12;

    }

    执行后,d1.d=12

    49 类型转换函数

    类型转换函数是用来对类型进行强制转换的一种成员函数,它是类的一种非静态成员函数。

    #include <iostream.h>

     

    class R

    {

    public:

    R(int d,int n){den=d;num=n;}

    operator double(){return double(den)/double(num);}

    private:

    int den,num;

    };

     

    void main()

    {

    R r(8,5);

    double d=3.5,f=2.6;

    d+=r-f;

    }

    执行后d=2.5

    分析:d和f都是double变量,r是类R的一个对象,它们具有不同的类型,它们之间之所以能够进行算术运算,得益于转换函数operator double()。由于类中定义了该类型转换函数,系统将r转换为double类型后再进行计算。

    在程序中使用类型转换函数,必须注意:

    1 转换函数是成员函数,必须是非静态的

    2 在定义类型转换函数时不用带类型说明,函数名就是类型转换的目标类型

    3 转换函数是用来进行类型转换的,定义时不必带任何参数

    4 转换函数不能定义为友元函数。

    50 在一个函数体内定义的类称为局部类,该类只能在定义它的函数体内使用,超出该函数体将不可见。定义局部类时,应该注意,不能在类中声明静态的成员函数,并且所有成员函数的定义都必须在类体内实现。

    51 多个基类构造函数的执行顺序取决于定义派生类时所指定的各个基类的顺序,而与派生类构造函数的成员初始化列表中给定的基类顺序无关。

    52 构造函数不能说明为虚函数,因为这样做没意义。而析构函数可以声明为虚函数。虚析构函数的作用在于当使用运算符delete删除一个对象时,能确保析构函数被正确的执行。因为设置虚析构函数可以采用动态联编,于是可在运行时选择析构函数。

    53 函数模板是对一组函数的描述,它不是一个实实在在的函数,编译系统不会产生任何执行代码。

    当编译系统在程序中发现有与函数模板形参表中相匹配的函数调用时,便生成一个重载函数,该重载函数的函数体与函数模板的函数体相同。该重载函数称作模板函数。

    函数模板与模板函数的区别如下:

    函数模板不是一个函数,而是一组函数的模板,在定义中使用了参数化类型。

    模板函数是一种实实在在的函数定义,它的函数体与某个函数模板的函数体相同。编译系统遇到模板函数调用时,将产生可执行代码。

    函数模板是定义重载函数的一种工具。一个函数模板对某种类型的参数生成一个模板函数,对不同类型的模板函数是重载的。这样就似的一个函数只需编码一次就能用于某个范围的不同类型的对象上。因此可以说函数模板是提供一组重载函数的样板。

    54 在cin输入流中以空格作为字符串分隔符,以Ctrl+Z作为结束符。

    55 goto语句的错误用法:

    C++中不能用goto语句跳过包含隐式或显式初始化(注意不是与赋值的区别)的变量声明语句。C++在类对象构造时,要对其进行复杂的初始化,在类对象 撤销时,要做必要的析构处理。例如,类对象可以在初始化时分配内存,如果跳过某个对象的初始化过程,那么它在被撤销时,编译器会认为对象已经被初始化了而 自动调用析构过程,这样就会引发不可预料的结果。例如:

    int main()

    {

    Cout<<”compute a random number”<<endl;

    Char ans;

    Cin>>ans;

     

    If(ans==’n’)

    goto done;

     

    int ran=rand(); //改正:int ran; ran=rand();

    cout<<ran;

    done:

    return 0;

    }

    该程序无法通过编译,因为goto跳过了对变量ran的初始化。而在C中这种跳转是允许的。

    56 void型指针可以指向任何类型的变量,任何地址都可以赋值给void型指针,但是如果没有强制类型转换说明就无法用void型指针得到变量的值。同样, 如果没有强制类型转换说明,也不可以对void指针进行指针的算术运算。函数的形参采用了void指针,就能够处理任何类型的内存。作为函数返回值的 void型指针可以赋值给其他任何类型的指针。

    57 标准的C的std::malloc函数在没有内存可分配时将返回空指针,因此C程序在调用malloc后,通常要检查返回指针是否为空,并做相应的处理。至于C++ 的new运算符,如果系统没有足够的内存可以分配的话,将抛出一个运行时异常。

    58 #line指令可以改变编译器用来指出警告和错误信息的文件名和行号

    59 ##运算符

    ##运算符用于把参数连接到一起,预处理程序把出现在##两侧的参数合并成一个符号。例如:

    #define join(a,b,c) a##b##c

    60 各种数据类型与0值的比较

    1 布尔类型 flag为布尔类型变量

    if(flag)

    if(!flag)

    2 整型变量 value

    If(value==0)

    If(value!=0)

    3 float型 e为允许误差

    if(value>= -e && value<= e)

    4 指针类型 p

    If(p==NULL)

    If(p!= NULL)

    61 枚举常量不会占用对象的存储空间,它们在编译时被全部求值。

    62 (1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安

    全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应)。

    (2) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。

    63 如果输入参数以值传递的方式传递对象,则宜改用“const &”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率。

    64 如果函数返回值是一个对象,要考虑return 语句的效率。例如

    return String(s1 + s2);

    这是临时对象的语法,表示“创建一个临时对象并返回它”。不要以为它与“先创建

    一个局部对象temp 并返回它的结果”是等价的,如

    String temp(s1 + s2);

    return temp;

    实质不然,上述代码将发生三件事。首先,temp 对象被创建,同时完成初始化;然

    后拷贝构造函数把temp 拷贝到保存返回值的外部存储单元中;最后,temp 在函数结束

    时被销毁(调用析构函数)。然而“创建一个临时对象并返回它”的过程是不同的,编译器直接把临时对象创建并初始化在外部存储单元中,省去了拷贝和析构的化费,提高了

    效率。

    65,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。

    66 free delete 释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。

    67 数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而

    不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。

    68,字符数组a 的容量是6 个字符,其内容为hello\0a 的内容可以改变,如a[0]= ‘X’。指针p 指向常量字符串“world”(位于静态存储区,内容为world\0),常量字符串的内容是不可以被修改的。从语法上看,编译器并不觉得语句p[0]= ‘X’有什么

    不妥,但是该语句企图修改常量字符串的内容而导致运行错误。

    char a[] = hello;

    a[0] = X;

    cout << a << endl;

    char *p = world; // 注意p 指向常量字符串

    p[0] = X; // 编译器不能发现该错误

    cout << p << endl;

    69 不 能对数组名进行直接复制与比较。若想把数组a 的内容复制给数组b,不能用语句 b = a ,否则将产生编译错误。应该用标准库函数strcpy 进行复制。同理,比较b 和a 的内容是否相同,不能用if(b==a) 来判断,应该用标准库函数strcmp进行比较。语句

    p = a 并不能把a 的内容复制指针p,而是把a 的地址赋给了p。要想复制a的内容,可以先用库函数malloc 为p 申请一块容量为strlen(a)+1 个字符的内存,再用strcpy 进行字符串复制。同理,语句if(p==a) 比较的不是内容而是地址,应该用库函数strcmp 来比较。

    // 数组

    char a[] = "hello";

    char b[10];

    strcpy(b, a); // 不能用 b = a; 编译不能通过

    if(strcmp(b, a) == 0) // 不能用 if (b == a)

    // 指针

    int len = strlen(a);

    char *p = (char *)malloc(sizeof(char)*(len+1));

    strcpy(p,a); // 不要用 p = a;

    if(strcmp(p, a) == 0) // 不要用 if (p == a)

    70 注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。

    char a[] = "hello world";

    char *p = a;

    cout<< sizeof(a) << endl; // 12 字节

    cout<< sizeof(p) << endl; // 4 字节

    …………………………………

    void Func(char a[100])

    {

    cout<< sizeof(a) << endl; // 4 字节而不是100 字节

    }

    71 如果函数的参数是一个指针,不要指望用该指针去申请动态内存。示例7-4-1 中,

    Test 函数的语句GetMemory(str, 200)并没有使str 获得期望的内存,str 依旧是NULL,为什么?

    void GetMemory(char *p, int num)

    {

    p = (char *)malloc(sizeof(char) * num);

    }

    void Test(void)

    {

    char *str = NULL;

    GetMemory(str, 100); // str 仍然为 NULL

    strcpy(str, "hello"); // 运行错误

    }

    示例7-4-1 试图用指针参数申请动态内存

    毛病出在函数GetMemory 中。编译器总是要为函数的每个参数制作临时

  • 相关阅读:
    课堂练习四
    手头软件产品的评价
    学习进度条十
    典型用户和用户场景描述
    学习进度条九
    学习进度条八
    冲刺第十天
    冲刺第九天
    冲刺第八天
    冲刺第七天
  • 原文地址:https://www.cnblogs.com/langqi250/p/2761613.html
Copyright © 2020-2023  润新知