指针

1. 指针的定义

指针是程序数据在内存中的地址

指针变量就是指向某个内存地址的变量

例如:

int a; //定义一个int型的变量a,系统为其分配内存空间,大小是int数据类型所需的内存大小(和编译器有关)

a的含义就是:

1、这个变量的名字
2、变量里存储的内容
3、如果想知道系统把哪块内存分配给a了?

&:取址符就发挥了作用,&a就代表a这块空间的地址,沿着这个地址往后的4个字节就是a代表的内存空间

以定义一个整型指针为例:

int *p; //这个内存空间的大小是int *类型所需的内存大小

p是指针变量,&a是一个具体的地址,那么我们可以有下边的操作:

p=&a;

或者在定义p的时候就赋值

int *p=&a;

常用指针定义介绍

int* p_int; //指向int类型变量的指针
Student* p_struct; //类或结构体类型的指针
int** p_pointer; //指向一个整形变量指针的指针
int(*p_arr)[3]; //指向含有3个int元素的数组的指针
int(*p_func)(int,int); //指向返回类型为int,有2个int形参的函数的指针,由于“*”的优先级低于“()”的优先级,*p_func如果不加括号,则变成了返回值是int型的指针

指针数组:
首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。它是"储存指针的数组"的简称。

数组指针:
首先它是一个指针,它指向一个数组。在64位系统下永远是占8个字节,至于它指向的数组占多少字节,不知道。它是"指向数组的指针"的简称.

下面到底哪个是数组指针,哪个是指针数组呢?

A) int *p1[10];
B) int (*p2)[10];
// 哪个是指针数组? 这里需要明白一个符号之间的优先级问题。
// "[]"的优先级比"*"要高。p1先与"[]"结合,构成一个数组的定义,数组名为p1,int *修饰的是数组的内容,即数组的每个元素。那么可知,p1是一个数组,其包含10个指向int类型数据的指针,即指针数组。
// 至于p2,"()"的优先级比"[]"高,"*"号和p2构成一个指针的定义,指针变量名为p2,int修饰的是数组的内容,即数组的每个元素。

2. 指针的初始化

初始化是让指针指向一个合法有效的内存空间。
分配空间的方法有两种:
静态分配:让指针指向一个已经存在的内存空间,如:int a; 这里的int在系统中已经分配了4个内存空间
动态分配:用函数分配空间,在C语言是使用malloc(),而在C++中则可以用malloc()和new()

3. 解析地址对象

如果指针指向了一个对象,则允许使用解引用符(*)来访问该对象。对于结构体和类,可以使用->符号(指针的指向运算符)访问内部成员:

4. 指针值的状态

(1) 指向一个对象的地址。
(2) 指向紧邻对象所占空间的下一个位置:用于迭代器和指针的计算
(3) 空指针,意味着指针没有指向任何对象:在C语言中,让指针变量赋值为NULL表示一个空指针,C语言中,NULL实质是 ((void*)0),在C++中,NULL实质是0。
(4) 无效指针(野指针),上述情况之外的其他值:

野指针指向一个已删除的对象或未申请访问受限内存区域的指针。

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

1)指针定义时未被初始化:指针在被定义时,如果程序不对其进行初始化的话,它会指向随机区域,因为任何指针变量(除了static修饰的指针变量)在被定义的时候是不会被置空的,它的默认值是随机的。

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

2)指针被释放(free或者delete)时没有被置空:在用malloc开辟内存空间时,要检查返回值是否为空,如果为空,则开辟失败;如果不为空,则指针指向的是开辟的内存空间的首地址。

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

3)指针操作超越变量作用域:不要返回指向栈内存的指针或引用,因为栈内存在函数结束的时候会被释放:

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

5. void*指针

   void* 指针是一种特殊的指针类型,可用于存放任意对象的地址,但是丢失了类型信息。如果想要完整的提取指向的数据,程序员就必须对这个指针做出正确的类型转换,然后再解指针。因为,编译器不允许直接对void*类型的指针做解指针操作(提示非法的间接寻址)。
   利用void所做的事儿比较有限:拿它和别的指针比较、作为函数的输入或输出,或者赋给另外一个void对象。

6. 指针的算术运算

指针是一个用数值表示的地址。可以对指针进行四种算术运算:++、--、+、-。
1) 递增一个指针:

ptr++ : ptr每增加一次, 移动指针到下一个内存位置
在程序中使用指针代替数组,因为变量指针可以递增,而数组不能递增,因为数组是一个常量指针(可以通过下标名访问)。

2) 递减一个指针:

对指针进行递减运算,即把值减去其数据类型的字节数

3) 指针可以用关系运算符进行比较:

如 ==、< 和 >。如果 p1 和 p2 指向两个相关的变量,比如同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较,比较的是变量指针所指向的地址

4) 指针之间的赋值:

指针赋值和int变量赋值一样,就是将地址的值拷贝给另外一个。指针之间的赋值是一种浅拷贝,是在多个编程单元之间共享内存数据的高效的方法。

int* p1  = &a;
int* p2 = p1;
// p1和p2所在的内存地址不同,但是所指向的内存地址相同。

7. 指针 vs 数组

(1) 数组在某些情况下可以转换为指针,当数组名在表达式中使用时,编译器会把数组名转换为一个指针常量,是数组中第一个元素的地址,类型是数组元素的地址类型
如:

int a[5]={0,1,2,3,4};
// 数组名a若出现在表达式中,如int *p=a;那么它就转换为第一个元素的地址,等价于int *p=&a[0];

(2) 只有在两种场合下,数组名并不用指针常量来表示
使用sizeof操作符操作计算数组的大小时:sizeof返回整个数组的长度,使用的是它的类型信息,而不是地址信息,不是指向数组的指针的长度。
使用取址符&时: 取一个数组名的地址所产生的是一个指向数组的指针,而不是指向某个指针常量值的指针。

如对数组a,&a表示的是指向数组a的指针,类型是int (*) [5],所以int p=&a;是不对的,因为右边是一个整形数组的指针int ()[5],而p是一个整形指针int *;

数组可以使用下标访问元素:如a[3],用下标来访问数组a中的第三个元素,等价于*(a+3)

8. 指向指针的指针

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

#include <iostream>
using namespace std;
int main ()
{
    int var;//普通int型变量
    int *ptr;//指向int型变量的指针
    int **pptr;//指向指针的指针
    var = 3000;

    // 获取 var 的地址
    ptr = &var;

    // 使用运算符 & 获取 ptr 的地址
    pptr = &ptr;

    // 使用 pptr 获取值
    cout << "var 值为: " << var << endl;
    cout << "*ptr 值为: " << *ptr << "  ptr 值为: " << ptr << endl;
    cout << "**pptr 值为: " << **pptr << "  pptr值为: " << pptr << "  *pptr值为: " << *pptr << endl;

    return 0;
}
// 结果:
// var 值为: 3000
// *ptr 值为: 3000  ptr 值为: 0x7ffee2262ac8
// **pptr 值为: 3000  pptr值为: 0x7ffee2262ac0  *pptr值为: 0x7ffee2262ac8

9. 传递指针给函数

例如:

void doubleValue(int *val)
{
    *val *= 2;
}
// 目的是使val指向的变量翻倍。

当val被解引用时,*= 运算符对val指向的变量起作用。该语句可以将地址存储在val中的原始变量乘以2。当调用该函数时,必须使用被翻倍的变量地址作为实参,而不是变量本身作为实参。

10. 从函数返回指针

注意:不要把非静态局部变量的地址返回。局部变量是在栈中的,由系统创建和销毁,返回之后的地址有可能有效也有可能无效,这样会造成bug。
可以返回全局变量、静态的局部变量、动态内存等的地址。

11. const与指针

指针常量和常量指针,两者的区别是看const修饰的谁。

1)常量指针

int a = 1;
int b = 2;
int* const p = &a;  //实际是个指针,指针本身是个常量。
*p = 98;  //正确
p = &b;  //编译出错,因为p指针是const

常量指针必须初始化,而且一旦初始化完成,则它的值就不能改变了。

2)指向常量的指针

int a = 1;
int b = 2;
const int* p = &a; // 
int const *p = &a; //两者的含义是一样的
*p = 98; //编译出错,*p是const
p = &b; //正确

指向常量的指针要求不能通过该指针改变对象的值,但是对象的值可以通过其它途径进行改变。
注意:对于常量变量,必须使用指向常量的指针来指向,对于非常量变量,两者都可以。

const int a = 1;
const int* p = &a;//正确
int* q = &a;//错误

12. 浅拷贝和深拷贝

1)浅拷贝:拷贝对象的地址(指针变量的值)
浅拷贝是对内存地址的复制,让目标对象指针和源对象指针指向同一片内存空间。
2)深拷贝:拷贝指针所指向内存空间
深拷贝是指拷贝对象的具体内容,而内存地址是自主分配的,拷贝结束之后,两个对象虽然存的值是相同的,但是内存地址不一样,两个对象也互不影响,互不干涉。

#include <iostream>
using namespace std;
int main ()
{
    int m = 5;
    int n = 6;
    int* a = &m;
    int* b = &n;

    // a = b; // 浅拷贝
    // cout << "a的地址: " << a << "  a的值: " << *a << endl;
    // cout << "b的地址: " << b << "  b的值: " << *b << endl;
    // 结果
    // a的地址: 0x7ffee644dab4  a的值: 6
    // b的地址: 0x7ffee644dab4  b的值: 6

    *a = *b; // 深拷贝
    cout << "a的地址: " << a << "  a的值: " << *a << endl;
    cout << "b的地址: " << b << "  b的值: " << *b << endl;
    // 结果
    // a的地址: 0x7ffee46b1ad8  a的值: 6
    // b的地址: 0x7ffee46b1ad4  b的值: 6
    return 0;
}

13. 智能指针

参考网站:
https://www.cnblogs.com/wxquare/p/4759020.html

posted on 2020-06-01 12:20  JJ_S  阅读(156)  评论(0编辑  收藏  举报