再补充一些关于指针的注意点.

一.野指针及C++指针使用注意点

避免野指针的产生

“野指针”的成因主要有:

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

1
char *p; //此时p为野指针

2)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针.

1
2
3
char *p=new char[10];  //指向堆中分配的内存首地址,p存储在栈区
cin>> p;
delete []p; //p重新变为野指针

3)指针操作超越了变量的作用范围。

1
2
3
char *p=new char[10]; //指向堆中分配的内存首地址
cin>> p;
cout<<*(p+10); //可能输出未知数据

指针的注意点:

a.指针指向常量存储区对象

1
char *p="abc";

此时p指向的是一个字符串常量,不能对*p的内容进行写操作,如srtcpy(p,s)是错误的,因为p的内容为“abc”字符串常量,该数据存储在常量存储区,但可以对指针p进行操作,让其指向其他的内存空间。

b.资源泄漏

问题:

1
2
3
4
5
6
7
8
9
#include<iostream>

using namespace std;

void main() {
char *p = new char[3]; //分配三个字符空间,p指向该内存空间
p = "ab"; //此时p指向常量“ab”,而不再是new char分配的内存空间了,从而造成了资源泄漏
delete[]p; //释放时报错
}

结果:报错

改进:

1
2
3
4
5
6
7
8
#include<iostream>
using namespace std;
void main()
{
char *p=new char[3]; //分配三个字符空间,p指向该内存空间
strcpy(p,"ab"); //将"ab"存储到p指向的内存空间
delete []p; //ok
}

c.内存越界

1
2
3
1 char *p=new char[3];  //分配三个字符空间,p指向该内存空间
2 strcpy(p,"abcd"); //将abcd存处在分配的内存空间中,由于strlen("abcd")=4>3,越界
3 delete []p; //ok

d.返回值是指针

问题:数组p[]中的内容为“hello world”,存储在栈区,函数结束时内容被清除,p变为野指针,可能导致乱码

1
2
3
4
5
6
7
8
9
10
11
#include<iostream>
using namespace std;
char *f()
{
char p[]="abc";
return p;
}
void main()
{
cout<<f()<<endl;
}

有的编译器会报错,有的会执行但是出现乱码.

改进:

1.加static限定,延长数组生存期

1
2
3
4
5
6
7
8
9
10
11
#include<iostream>
using namespace std;
char *f()
{
static char p[]="abc"; //此时数组为静态数组,存储在全局/静态区,生存期到程序结束,因此函数结束时不会销毁p
return p;
}
void main()
{
cout<<f()<<endl;
}

2.定义成指针型数组

1
2
3
4
5
6
7
8
9
10
11
#include<iostream>
using namespace std;
char *f()
{
char *p="abc"; //"abc"存储在文字常量区,p是指向常量的指针,生存期到程序结束
return p;
}
void main()
{
cout<<f()<<endl;
}

e.指针做形参

即所谓的地址传递,我们都知道地址传递的方式,形参的改变会导致实参的改变,但要注意的是,这里的改变是指指针所指内容的改变,而不是指针值的改变。因此,当形参改变会导致实参改变时,指针所指的内容是非const类型的,否则会出错。

1.改变指针内容:

1
2
3
4
5
6
7
void swap(int *a,int *b)   //交换的是*a,*b,即指针的内容,而不是指针a,b
{
int t;
t=*a;
*a=*b;
*b=t;
}

2.改变指针值:

1
2
3
4
5
6
7
8
9
10
11
12
#include<iostream>
using namespace std;
void fun(char *p)
{
p="cba"; //“cba”存放在文字常量区,让p指向常量"abc",这里改变的是指针值,实参并不会改变
}
void main()
{
char *p="abc"; //“abc”存放在文字常量区,p指向常量"abc"
fun(p);
cout<<p<<endl; //输出"abc",而不是"cba"
}

上面的代码会报错,不能将 “const char *” 类型的值分配到 “char *” 类型的实体,所以改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<iostream>
using namespace std;
void fun(const char *p)
{
p = "cba"; //“cba”存放在文字常量区,让p指向常量"abc",这里改变的是指针值,实参并不会改变
}
void main()
{
const char *p = "abc"; //“abc”存放在文字常量区,p指向常量"abc"
fun(p);
cout << p << endl; //输出"abc",而不是"bca"
system("pause");
}

这样的代码是失败的,因为VS告诉我们,Parameter ‘p’ is only assigned but neveraccessed.直接把第五行删了也没问题.

二.指针和数组

说到指针和数组,这里直接说三个最基本的规则,这三个规则基本上能够概括指针和数组的常见关系了.

  1. 一看到数组,就要知道数组名可以当做这个数组的第一个地址。(a=&a[0]
  2. 对于指向数组的指针,指针+n表示往后移动n个位置
  3. 指针也可以像数组那样用p_a[n]这种形式直接取元素,本质是*(p_a+n)

三.指针和字符串

指针和字符串相信是很多人一直弄混淆的地方了.其中确实有些地方需要理一理. 指针和字符串主要是指针用于c风格的字符串.

这里首先把重要的几个规则列出来,这几个规则足够弄清楚指针和字符串之间的关系了.

  1. 已经知道,c风格的字符串本质就是一个字符数组,所以,数组名就是第一个元素的地址。同样,一个指向char的指针变量也能够实现字符串的一些东西.
  2. C++中,对于引号引起来的字符串,也代表第一个元素的地址
  3. cout对象认为,char的地址是字符串的地址,因此它打印该处的地址处的字符, 然后继续打印后面的字符。直到遇到空字符’\0’之后才停止。

这里举一个例子来详细说明.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <cstring>

int main()
{
char animal[20]="bear";
const char* bird="wren";
char* p_s;

//数组名也是指针
std::cout<<animal<<" and "<<bird<<std::endl;

//直接输出指针
p_s=animal;
std::cout<<"p_s:"<<p_s<<std::endl;

//想要输出真的地址怎么办?
std::cout<<"address of animal:"<<(int*)animal<<std::endl;

return 0;
}
  1. 永远应该记住,双引号引起来的C风格的字符串常量,字符数组,字符指针变量这几个是相通等价的。
  2. 注意const char* bird="wren";要是不使用const会有警告,因为不能够用可变的指针来修改常量.
  3. 当cout后面是其他的指针的时候,会输出该指针的值也就是一个地址,但是cout遇到char* 的时候,就会输出当前char*指向的值,同时也会输出后面的值,直到遇到空字符为止。
  4. 所以在上面程序的20和21行有一个(int*)强制转化为整形指针,不然就没得输出了。