【C++学习笔记】七、指针的使用

本文主要是谭浩强老师c++教材第三版第六章的小结。

1.变量与指针

(1)指针的定义

指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:

type *var-name;

在这里,type 是指针的基类型,它必须是一个有效的 C++ 数据类型,var-name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:

int    *ip;    /* 一个整型的指针 */
double *dp;    /* 一个 double 型的指针 */
float  *fp;    /* 一个浮点型的指针 */
char   *ch;    /* 一个字符型的指针 */

所有指针的值的实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。在Visual C++中为每个指针变量分配4个字节的存储空间。

在定义指针变量时需注意:

a. 在定义指针变量时必须指定基类型,只有知道数据类型,才能按该类型的长度从正确长度的地址中读取该数据;

b. 不能用一个整数给指针变量赋值,系统将不会认为这是地址,而认为这是整数,从而报错,如:

int *point_1 = 2000 //非法

int i;

int *point_1 = &i //合法

(2)引用指针变量

两个运算符:

&:表示取地址运算符

*:表示指针运算符(或间接访问运算符)

取地址和赋值运算例子:

int *point_a,a;

a=100;

point_a = &a //取地址表达式

*point_a = a //赋值表达式

对于&和*运算符的说明:

这两个运算符优先级相同,但是按照从右向左的方向结合:

如:&*point_a的含义是先用*取出a的值,再用&取a的地址,因此它等价于&a;

同理:*&a的含义是先用&a取出a的地址,再用*取值,因此它等价于变量a。

关于指针变量*point_1符号在定义和引用时的区别:

在定义时,*point_1代表了一个指针变量,因此不能用数值给它赋初值,它会将数值当做地址从而报错,而应该将取地址符&a赋给它作初值;

在引用时,*point_1代表了所指向元素a的值,因此地址&a应该赋给point_1,而不是*point_1,详情见如下例子:

#include <iostream>
using namespace std;
int main()
{
	int a,b;
	a = 100,b = 10;
	int *point_1 = &a;
	int *point_2;
	point_2 = &b;
	cout << *point_1 << "\t";
	cout << *point_2;
}

输出为100,10

(3)用指针作函数参数

指针变量用作函数时,实参和形参变量始终是“值传递”的过程,因此形参中若传入的是地址则只改变地址,而不改变原地址的指向,若传入的是值则只改变值,而不改变实参的指向。具体分析看下边三个例子:

题目:找出两数中的大数和小数

主函数:

#include <iostream>
using namespace std;
int main()
{
    void swap(int *p1, int *p2);
    int *point_1, *point_2,a,b;
    cin >> a >>b;
    point_a = &a;
    point_b = &b;
    if(a<b) swap(point_a,point_b);
    cout << "max = " << a <<" min = " << b <<endl;
    return 0; 
}

主函数要注意的地方是:

引用函数语句:if(a<b) swap(point_a,point_b)这里放的是指针变量,不能写成*point_a,因为*在引用里表示取值,会造成变量类型不符。

swap函数正确示例如下

void swap(int *p1, int *p2)
{
    int temp;
    temp = *p1;
    *p1 = *p2;
    *p2 = temp;
}

指针变量做实参传给形参,在形参中交换了指针变量所指向的值,因此可以实现a,b值的改变

输入a=45,b =78

运行结果为:max=78, min =45

变量改变过程如下:

此处要注意一个貌似对实际不对的写法

void swap(int *p1, int *p2)
{
    int *temp;
    *temp = *p1;
    *p1 = *p2;
    *p2 = *temp;
}

此处将temp作为一个指针变量,它的错误点在于:未对temp赋值,因此它是不可预见的,这种情况下对*temp赋值是有危险的,会破坏系统工作状况,也就是说不能定义一个指针变量后,对它所指向的值直接赋值,而应该对它先赋地址值,因此要用一个整型变量temp来接收值。

由于交换地址值所造成的错误写法

void swap(int *p1, int *p2)
{
    int *temp;
    temp = p1;
    p1 = p2;
    p2 = temp;
}

当输入a = 45,b=78时,输出的结果是:

max= 45,min =78

出现该错误的原因是:只改变了实参指针变量的值,但并没有改变指针的指向,也就是单纯换了地址,并没有换值,分析过程如下:

最后是一种不用指针单纯用整型变量的错误写法

void swap(int x, int y)
{
    int temp;
    temp = x;
    x = y;
    y = temp;
}

这是一般的参数传递,是单向的值传递过程,只能从实参传给形参,形参的改变无法传回给实参,具体变化如下:

一般变量与指针变量做实参的区别在于:

指针变量做实参时,传给形参的是地址,相当于二者共用了一段地址,因此改变形参地址中的值一定会改变实参的地址中的值

而一般变量做实参时,形参只是与实参有相同值的不同变量,因此形参改变不影响实参的改变。

总而言之,要改变实参的值,一定要使指针变量指向值发生改变。

2. 数组与指针

即指向数组元素的指针。

int a[10];

int *p;

以下两个语句等价:

p = & a[0];

p = a;

数组名代表数组首元素的指针,p+1则代表下一个元素的指针

因此引用数组元素时,可以使用下标法,如a[i],也可使用指针法,如*(a+i)或者*(p+i)

指针也可以有自增和自减算法,如p++,这种方法由于不用每次重新计算地址,是最快的方式

使用指针数组时,a[10]不是合法元素,但是由于是找地址,也就是p+10系统不认为它非法,因此要注意避免这种情况。

至于做参数的情况,与上述指针变量的理解一致。

3. 字符串与指针

字符串的初始化方法:

(1)用字符数组存放一个字符串:

如:char str[] = "I love China!"

(2)用字符串变量存放一个字符串:

如:string str = "I love China!"

(3)用字符指针指向一个字符串

如:char  *str = "I love China!"

或者:

char *str;

str = "I love China!"

4. 函数与指针

指针变量也可以指向一个函数,一个函数在编译时被分配给一个入口地址,这个入口地址就称为函数的指针。可以用一个指针变量指向函数,然后通过该指针变量调用此函数。

定义形式:

函数类型 (* 变量名)(函数的形参列表)

例如:

求a和b中的大者

#include <iostream>
using namespace std;
int main()
{
    int max(int x, int y);
    int (*p)(int, int);
    int a,b,m;
    p =max;
    cin>>a>>b;
    m = p(a,b);
    cout <<"max= "<<m<<endl;
    return 0;
}

int max(int x, int y)
{
    int z;
    if(x>y)z=x;
    else z=y;
    return(z);
}

5.返回指针值的函数

C++ 允许从函数返回指针,可以理解为指针型函数。为了做到这点,必须声明一个返回指针的函数,如下所示:

类型名 *函数名(参数列表)

如:int *(int x, int y)

6. 指针数组

如果一个数组的元素均为指针类型数据,则称该数组为指针数组。

定义为:

类型名 *数组名[数组长度]

如:int *p[4]

#include <iostream>
 
using namespace std;
const int MAX = 3;
 
int main ()
{
   int  var[MAX] = {10, 100, 200};
   int *ptr[MAX];
 
   for (int i = 0; i < MAX; i++)
   {
      ptr[i] = &var[i]; // 赋值为整数的地址
   }
   for (int i = 0; i < MAX; i++)
   {
      cout << "Value of var[" << i << "] = ";
      cout << *ptr[i] << endl;
   }
   return 0;
}

在这里,把 ptr 声明为一个数组,由 MAX 个整数指针组成。因此,ptr 中的每个元素,都是一个指向 int 值的指针。

7.指向指针的指针

指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。

C++ 中指向指针的指针

一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 int 类型指针的指针:

int **var;

实例:

#include <iostream>
 
using namespace std;
 
int main ()
{
    int  var;
    int  *ptr;
    int  **pptr;
 
    var = 3000;
 
    // 获取 var 的地址
    ptr = &var;
 
    // 使用运算符 & 获取 ptr 的地址
    pptr = &ptr;
 
    // 使用 pptr 获取值
    cout << "var 值为 :" << var << endl;
    cout << "*ptr 值为:" << *ptr << endl;
    cout << "**pptr 值为:" << **pptr << endl;
 
    return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

var 值为 :3000
*ptr 值为:3000
**pptr 值为:3000

更为经典的例子:

#include <iostream>
using namespace std;
int main()
{
    char **p;
    char *name[] = {"BASIC","FORTRAN","C++"};
    p = name +2;
    cout << *p;
    cout << **p;
}

运行结果:

C++

C

解释:由于p = name+2 = name[2],因此p中存放的是name[2]首字符的地址,因此取值cout <<*p所得的是从name[2]中依次输出字符,而**p输出的是*p的值,也就是C++的第一个字符。

8. 空指针

在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为指针。空指针是“指向空类型”或者“指向不确定类型”

NULL 指针是一个定义在标准库中的值为零的常量。请看下面的程序:

#include <iostream>

using namespace std;

int main ()
{
   int  *ptr = NULL;

   cout << "ptr 的值是 " << ptr ;
 
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

ptr 的值是 0

在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。

9.引用

对于引用可以理解为为变量取了一个别名,它是c++的重要扩充。

引用使用引用声明符来实现的,具体例子如下:

int a;

int &b = a;//声明b是a的引用,也就是说给a取了个别名叫做b

可以理解为:通过b可以引用a;

此处&是引用声明符而不是地址运算符,此处的区别主要是:出现在声明中的&是引用声明符,其他情况是地址运算符

如:

int &b =a; //声明b是a的引用;

cout << &b; // 输出b的地址。

对于引用需要注意的几点:

a. 引用不是一种独立的数据类型,它只是个别名,因此对于引用只有声明,没有定义

b. 声明一个引用时,必须同时对其初始化,即声明它为哪一个变量的引用;

c. 在声明一个引用后,不能作为另一个变量的引用;

d. 不能建立引用数组,既不能引用数组元素,也不能引用数组名,也不能建立指针的引用

e. 可以取引用的地址:

如:

int a;

int &b = a;

int *pt;

pt = &b;//取引用b的地址,也就是把a的地址赋给pt。

引用作函数参数时,系统会自动把实参的地址传给形参,因此是址传递。这可以理解为:当引用作形参时,实参传来之后相当于被引用改了个名字,值和地址都是实参本身,实参和形参公用存储空间,因此对于形参的改变就相当于在实参本身上改,因此会影响实参的值。

一般变量做参数、指针变量做参数、引用变量做参数的具体比较请见链接:

https://jingyan.baidu.com/article/8065f87fed913f23312498c3.html

本文主要参考:http://www.runoob.com/cplusplus/cpp-null-pointers.html

猜你喜欢

转载自blog.csdn.net/zl3090/article/details/86569865
今日推荐