C++ 笔记(四)

                                                 指针和数组

       指针是C/C++的精华,但在使用时也是一个一个比较让人头痛的问题,在我平时写程序过程中,指针也是产生问题最多的地方。依照书本,来记录一些指针和数组的重点。

       C++语言应尽量使用vector和迭代器类型,应避免使用低级的数组和指针,涉及良好的程序只有在强调速度时才在类实现的内部使用数组和指针。因此要向成为一个真正的C++程序员就要多使用vector和string来替代数组和c风格字符串。

        数组的维数必须用整型常量指定,非const类型或是到运行时才知道其值的const变量都不能用于定义数组的维数。

        在没有显式提供元素的初始时数组元素会像普通元素一样初始化。若在函数体外定义的数组,所有元素将会被初始化为0,若是在函数体外定义的数组,其元素不会被初始化。如果数组元素为类类型,无论数组在哪里定义,都会自动调用类的默认构造函数来初始化。如果类没有默认构造函数,则必须为数组的元素提供显式的初始化。这一点与vector类似。

        与vector中的size_type类似,数组下标的正确类型为size_t;头指针和尾指针只差的正确类型为ptrdiff_t,它是signed类型。

        使用指针不当易导致错误发生,应将未初始化的指针初始化为NULL,因为编译器可以测出0值的指针。这在一定程度上可以避免指针未初始化就使用的情况。

        void*类型的指针可以存储任何类型的指针。它表明该指针与某一地址值相关,但不清楚存储在此地址上的对象的类型。void*类型的指针只支持几种有限的操作:1,与另一个指针比较2,向函数传递void*指针或是从函数中返回void*指针。3,给另一个void*指针赋值。但是不允许使用void*操作它指向的对象。

        实际中常把指向const的指针用作函数形参,以此确保传递给该函数的实际对象不会再函数中被修改。这也说明了,指向const的指针,既可以指向const对象也可以指向非const对象。

       使用const定义变量时,const int a;与int const a;是等价的。这样就容易导致混淆。如     

        typedef int *IntPtr;

        const IntPtr pp;


       此时pp是什么类型呢。真正的类型应该是将pp定义为指向int类型的const指针。它等价于int * const pp;原因是在const  IntPtr  pp等价于IntPtr  const  pp, const作用于IntPtr类型。而不是跟const int a 一样作用于a。所以尽量写成IntPtr  const  pp这种形式,此时便不会误会。

       动态分配数组时可以在数组长度后面加一对空圆括号,对数组的元素进行初始化。此时数组元素都被初始化为0;如int *array=new int[10]();

        创建const对象的动态数组时必须为数组元素提供初始化,因为数组元素都是const对象。如const int *array=new const int[100]();

       动态分配的数组空间要调用delete []进行释放。漏用【】,编译器无法发现此错误,会导致少释放内存空间,导致内存泄露。

       String成员函数c_str()返回const类型的数组指针,因此不能被修改。另外一旦string对象被改变,c_str()返回的数组就会变为无效,也就是说c_str()返回的数组是在调用函数时才对string对象内容的拷贝,并不实时反映string对象的内容。

        可以使用数组为vector对象初始化。如int a[]={20,41,52,54,63,54,36};vector<int> v(a,a+7);两个指针标示出vector初值的范围,第二个指针指向数组最后一个元素的后一个地址空间。

        区分指针数组与行指针。如int *ptr[10],此为指针数组,int (*ptr)[10]此为行数组,p为指向具有10个元素的数组的指针。 

        平时经常使用typedef  int *ptr;来简化操作。但是对于typedef int int_array[4];很不熟悉,可以参照typedef int *ptr;来理解。Int_array p;定义一个具体有四个元素的数组。而int_array *p定义一个指向具有四个元素数组的行指针。

 

附加点东西:

数组名和指针:

  (1)数组名的内涵在于其指代实体是一种数据结构,这种数据结构就是数组;
  (2)数组名的外延在于其可以转换为指向其指代实体的指针,而且是一个指针常量(int a[2];a++;编译会出错);
  (3)指向数组的指针则是另外一种变量类型(在WIN32平台下,长度为4),仅仅意味着数组的存放地址!
  (4)数组名作为函数形参时,在函数体内,其失去了本身的内涵,仅仅只是一个指针;
  (5)数组名在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。

野指针

        野指针”不是NULL指针,是指向“垃圾”内存(不可用内存)的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if无法判断一个指针是正常指针还是“野指针”。有个良好的编程习惯是避免“野指针”的唯一方法。

野指针的成因主要有三种:

  一、指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。

  二、指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。free和delete只是把指针所指的内存给释放掉,但并没有把指针本身delete掉。通常会用语句if (p != NULL)进行防错处理。很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块。

  另外一个要注意的问题:不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。

       三、指针操作超越了变量的作用范围。这种情况也会产生野指针。

指针常量和常量指针(一帖子中看到的,感觉总结的不错)

原文:

在C/C++的学习中,有人经常搞不清楚“常量指针”和“指针常量”这两个概念。其实简单一点讲,“常量指针”所指向的地址上的数据是常量,而“指针常量”所指向的地址是常量,地址上面的数据是可以变化的。

首先,告诉大家一个小规则,就是像这样连着的两个词,前面的一个通常是修饰部分,中心词是后面一个词,怎么说呢,就像这里的常量指针和指针常量。
  常量指针,表述为“是常量的指针”,它首先应该是一个指针。常量指针,就是指向常量的指针,关键字 const 出现在 * 左边,表示指针所指向的地址的内容是不可修改的,但指针自身可变。
  指针常量,表述为“是指针的常量”,它首先应该是一个常量。指针常量,指针自身是一个常量,关键字 const 出现在 * 右边,表示指针自身不可变,但其指向的地址的内容是可以被修改的。
  再分开细细说明,常量指针,它是一个指针,什么样的指针呢?它是一个指向常量的指针,就是说我们定义了一个常量,比如 const int a=7; 那么我们就可以定义一个常量指针来指向它 const int*p=&a; 也可以分成两步,即 const int *p; p=&a; 那么它有什么作用呢?首先我们来说说常量的属性,因为我们的指针是指向常量的,常量和变量的变量的不同之处在于我们不能对其内容进行操作,具体说就是修改,而我们的指针是什么,它的内容本身是一个地址,设置常量指针指向一个常量,为的就是防止我们写程序过程中对指针误操作出现了修改常量这样的错误,应该如果我们修改常量指针的所指向的空间的时候,编译系统就会提示我们出错信息。总结一下,常量指针就是指向常量的指针,指针所指向的地址的内容是不可修改的。
  再来说说指针常量,它首先是一个常量,再才是一个指针。常量的性质是不能修改,指针的内容实际是一个地址,那么指针常量 就是内容不能修改的常量,即内容不能修改的指针,指针的内容是什么呀?指针的内容是地址,所以,说到底,就是不能修改这个指针所指向的地址,一开始初始化,指向哪儿,它就只能指向哪儿了,不能指向其他的地方了,就像一个数组的数组名一样,是一个固定的指针,不能对它移动操作,比如你使用 p++; 系统就会提示出错。但是它只是不能修改它指向的地方,但这个指向的地方里的内容是可以替换的,这和上面说的常量指针是完全不同的概念。作一下总结,指针常量就是是指针的常量,它是不可改变地址的指针,但是可以对它所指向的内容进行修改。对了,忘了说说它怎么用,举个小例子 int a; int * const p=&a; 也可以分开写 int a;int * const p; p=&a;
  当然,你也可以定义个一个指向常量的指针常量,就把上面的两个综合一下,表示如下
    
const int a=7; const int *const p=&a;
    
下面看几个简单的例子,可以说明他们的区别:
            
第一个

  void main(){
     char *str1={"Hello"};
     char *str2={"Hello World"};
     char * const ptr1 =str1 ;
     //指针常量--指针本身是常量,指向的地址不可以变化,但是指向的地址所对应的内容可以变化

    ptr1 =str2; //错误 因为这是一个常量指针,改变指向的地址了

     printf("%s n",*ptr1);
   }
 
// 编译错误    error C3892: 'ptr1' : you cannot assign to a variable that is const    



第二个

void main(){
    char *str1={"Hello"};
    char *str2={"Hello World"};
    char * const ptr1 =str1 ;
    //指针常量--指针本身是常量,指向的地址不可以变化,但是指向的地址所对应的内容可以变化

    *ptr1 ='A';// 正确 因为指向的地址的内容是可以变化的

    printf("%c n",*ptr1);
    }

 //输出  A

 

第三个

 void main(){
    char *str1={"Hello"};
     char *str2={"Hello World"};
     const char *ptr1 = str1;
   //常量指针--指向字符串常量,所指向的字符串内容不能变,但是指向的地址可以变化
    
    ptr1=str2;// 正确 因为指向的地址是可以变化的

    printf("%s n",ptr1);
    }
 
//输出 Hello World


第四个

 void main(){
    char *str1={"Hello"};
    char *str2={"Hello World"};
    const char *ptr1 = str2;
    //常量指针--指向字符串常量,所指向的字符串内容不能变,但是指向的地址可以变化
    
     ptr1='A';// 错误 因为指向的地址是内容是不可以变化的

     printf("%c n",ptr1);
     }

 
 //编译错误    error C2440: '=' : cannot convert from 'char' to 'const char *'


相信从上面四个简单的例子可以看出他们不一样的地方把,在这里要请大家注意一下的地方是:
指针常量的申明:const 放在* 和指针名之间 Type * const pointer ;

关于记忆技巧: 
对于区分const int *pa和int *const pa这两者, 
前者中,const直接修饰*(不考虑int,因为类型在这里是没影响的),说明*(解引用)这种行为具有常量性,即“不能靠解引用改变它指向的对象的值”,即指向常量的指针。 
后者中,const直接修饰pa,说明pa本身的值具有常量性,即常量指针。 
或者也可以这样来记忆: 
const int a;       // const常量 
const int *a; //指向常量的指针 
int *const a=&n; //常量指针 
你在纸上按顺序写下以上三行,记住它们的注释长度:短——长——短,分别对应着:const常量、指向常量的指针、常量指针这三种,应该就不会混淆了。 

个人认为以上记忆法比《Effective ++》条款21中推荐的划线分左右法更好记。 
最后再举个例子,与迭代器经常在一起用。
若希望迭代器所指向的东西不可变,则需要的是 const_iterator。

智能指针:

就不在这里讨论了吧,后文分解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值