C/C++ 指针、数组和字符串

本次学习指针、数组、字符串、引用的内存映像。

1.指针

         指针的本质:可以执行的程序是由指令、数据和地址组成的。当CPU访问内存单元的时候,不论是读取还是写入,首先要把内存单元的地址加载到地址总线AB上,同时将内存电路的读写控制设置成有效,然后内存单元中的数据通过数据总线(DB)流向寄存器。或者是结果寄存器中的值流线目标内存单元。这就是一个内存读写周期。

         内存单元地址就是我们的指针。指针是一个变量,他和我们使用的整形变量,字符变量等等没有什么本质的差别,不同的是他们的类型和值得含义,即解释方式。在二进制层面上,指针的值就是内存地址的单元,而变量又是引用内存单元值的别名,因此在语言层面指针就是变量的地址。

         只要我们掌握了对象的内存地址,也就是指针,我们可以在任何的地方对其指向的内存单元进行操作,无论是读写数据还是调用函数。这种灵活的方式正式指针的危险,因为可能指向无效的内存地址。当我们访问非法的内存地址的时候,就会出现运行出错的情况。

         指针的类型和支持的运算

         指针的类型是一个类型的名字加上一个 * 组合,如int * ,编译器认为这是一个指针,指向内存单元是4个字节,并且将其内容解释成为int类型的值。

         我们可以使用typedef来定义成批的指针变量类型.

         typedef int*        IntPtr;

typedef int**       IntPtrPtr;

typedef IntPtrPtr*  IntPtrPtrPtr;

typedef char*       CharPtr;

typedef void*       VoidPtr;

指针运算:

    ++:指向序列中的下一个元素;

    --:指向序列中的上一个元素;

    +i-i:

    同类型的两个指针相减:表示两个指针之间的元素个数。

    指针赋值,将一个指针派给另一个指针;

    指针比较 < > == != >= <= 最常用的是 == !=

int *pInt  = new int[100];

pInt += 50 ;//编译器会修改成为 pInt += 50 * sizeof(int);

void* 类型的指针不可以运算,只能够进行赋值比较和sizeof

对于void*指针,不可以使用* 提取void指针指向的值。

 

指针传递参数:

void func(int *p);

int iCount = 0;

func(&iCount); 使用&获取变量的地址,作为参数,传递给被调用的函数。

 

理解双指针传递和返回问题

void getMemory(char * &p , int num){

        p = new char[num];

}

char *ptr = NULL;

    getMemory(ptr, 100);

    cout << "sizeof(ptr)" << sizeof(ptr) << endl;

    ptr = "yangtengfei";

    cout << "strlen ( ptr) :" << strlen(ptr) << endl;

cout << "ptr" << ptr << endl;

 

void getMemory(char ** p , int num){

    *p = new char[num];

}

char * ptr = NULL;

getMemory(&ptr,100);

 

我们可以把一个对象的地址在整个程序中的各个函数之间传递,只需要保证每一次使用的时候他都会指向合法的地址和有效的内存单元。这就是指针容易被错误使用的一个原因:在指针传递的过程中,我们有可能将指针指向的内存空间释放掉了,但是我们还是继续使用指针,或者传递了一个局部对象的地址,当函数结束的时候,对象自动销毁了。

2.数组

    数组的本质:任何数组不论是动态创建还是静态声明,其中的元素的内存都是连续字节存放的,也就是说保存在一大块连续的内存区中,vector也是连续存放的。

    当我们使用下标读写数据的时候,都是转换成指针

    a[3] = 100;//  *(a+3) = 100;

    因为C/C++数组本身不会保存下标值和元素对象之间的对应关系,因此无法直接通过下标索引来定位真正的数组元素对象。如果在程序中直接使用指针的方式,可以减少编译的时间,但是会降低程序的可读性。

    数组名字的本身就是一个指针,而且是一个指针常量, 也就是 int a[10]; a等价于

int * const a。我们不可以将数组的名称指向其他地址,数组的名称就是指向数组中第一个元素的内存单元的首地址,也就是 a == &a[0]; 任何两个数组之间是不可以直接赋值的

    int a[10] = {1};

    int b[10] = {1,2,3,4,5,6};

    a= b ;//这样会报错。

 

声明数组的方式:

明确的指出他的元素的个数,编译器会按照给定的元素的个数分配存储空间;

不明确给出元素的个数,但是直接初始化,编译器会根据元素的个数分配内存空间;

同时给定元素的个数并且初始化。但是不允许几部指定元素的个数,又不直接初始化,因为编译器不知道要分配多少的空间。多余的元素空间直接初始化为0。

标准的c++c是不会对数组访问进行越界检查。因为程序编译的时候是无法检查数组下标的。

 

    二维数组

    行序优先的远足存储二维数组的。当我们访问元素 a[4][3] 实际上编译器会将地址转换.

    int a[2][3] = {{1,2,3},{4,5,6}};

    for (int i = 0;i< 6 ; i++){

        cout << *( *a + i) << " " ;

    }

    数组和指针之间的关系:

    int a[10]  <==> int * const a;

    int b[4][5] <==> int  (* const b) [5];

    int c[3][4][5] <==> int (*const c) [4][5]

   

    数组传值:数组是不可以从函数return的,但是数组可以作为参数传递给被调用的函数。

    void func(const int a[], int size);

    传递的a就是一个指针,传递数组的时候并不是将整个数组直接传递给被调用的函数,而是传递给的数组的名称,也就是数组的首地址。上面的函数编译器会把他改写成

    void  func(const int  * const a, int size);

    为什么需要指针传递数组呢 ? 数组在内存中是连续存放的,因此编译器可以通过地址计算出数组中的所有元素;处于性能考虑,如果将整个数组传递进被调用的函数,不仅需要大量的时间来复制数组,而且这个copy会占用大量的堆栈空间。

    对于多维的数组,我们需要指出除了第一位数组的之外的所有维的长度

    void func(const int a[][20],int line);

    动态的创建删除数组

    int *pInt  =  new int[20];

    delete [] pInt; 必须添加[],否则只是删除了第一个元素。

3.字符数组、字符指针、字符串

    字符数组就是字符变量的数组,而字符串则是以‘/0’结尾的字符数组,字符数组并不一定是字符串。对于字符串来说,他是可变长的,因此他无法记录自己的长度,但是如果表示字符穿的结尾?就是用\0结束符。因为字符串的连续性,编译器没有必要通过他的长度信息获取整个字符串,可以仅仅通过一个指向开头字符的字符指针就能实现对整个字符串的引用。

    char array[]=“hello” 需要一个字节保存\0

    对于字符数组,并不在乎中间和尾部有没有\0结束符,因为数组知道他有多少个元素,而且\0就是一个合法的char。对于字符指针引用一个字符数组。我们使用韩式strcpy strlen 并不知道这个字符来自一个字符数组,因为仅仅传递的是一个字符指针。如果我们指向的内存空间中存在一个\0字符,他就会知道找到这一个\0为止。如果没有\0的话,就会访问内存冲突,或者篡改其他的内存单元。

    字符指针的误区:

    C/C++默认使用char * 表示字符串

    char ch = 'a';

    char *pChar = & ch;

    cout << pChar << endl; 越界

    字符串的拷贝和赋值: 使用strcpy和 strncpy 不哟啊使用== = != 直接比较字符串

4函数指针

    C/C++连接器在连接程序的时候,必须将函数的首地址绑定到对该函数的调用语句上面,因此函数地址必须在编译的时候就确定下来,也就是在编译器为函数生成代码的时候。

    函数的地址就是一个编译时的常量,函数指针就是指向函数体的指针,其值就函数体的首地址。而在源代码层面上,函数的名称就是代表函数的首地址,所以讲函数名直接指派给一个同类型的函数指针而不需要使用&符号,也可以直接使用函数名称注册回调函数。

5.引用和指针的比较

    引用是在C++中的新提出的概念, 引用就是一个对象的别名:

    int  m = 10;

    int &n = m;

    引用在创建的时候必须初始化,即引用到一个有效的对象;而指针在定义的时候不必初始化,可以在后面初始化;

    不存在引用NULL,引用必须与合法的存储单元关联;但是指针式可以是NULL。 不要使用字面常量初始化引用。 const int & rIint = 0; 这样的话,这个数据会一直存在。

    引用一旦被初始化一个对象,它就不可以被改变为对另一个对象的引用,但是指针是可以的。

    引用的创建和销毁不会调用构造函数和析构函数

    在语言层面上,引用都是通过指针实现的

    引用既有指针的效率,又具有变量使用的方便性和直观性。

posted on 2013-11-05 22:33  追梦的飞飞  阅读(501)  评论(0编辑  收藏  举报