2019年四月之初识指针


二维数组表示四种方法:


` 1、 x [ i ] [ j ]                                                 
  2、*(*( x + i ) + j )                                        
  3、*( x [ i ] + j )                                            
  4、(*(x+i))[ j ]                                           `

辨析:

*(a + 1)[ 5 ]  与 *(a + 1 + 5) + 0 等价,即a[ 6 ][ 0 ]
(*(a + 3)) 与 *(*(a + 0) + 3)等价,即 a[ 0 ][ 3 ]
*(*(a + 1)) 与 *(*(a + 1) + 0)等价,即 a[ 1 ][ 0 ]
*(&a[ 0 ][ 0 ] + 2) 与 *(a[ 0 ] + 2)等价,即 a[ 0 ][ 2 ]
&a[ 0 ][ 0 ]=a=a[ 0 ]

( )改变优先级,左结合性

计算机中所有的数据都必须放在内存中,不同类型的数据占用的字节数不一样。例如 int 占用4个字节,char 占用1个字节。为了正确地访问这些数据,必须为每个字节都编上号码,每个字节的编号是唯一的,根据编号可以准确地找到某个字节。

下图是 4G 内存中每个字节的编号(以十六进制表示):
在这里插入图片描述
我们将内存中字节的编号称为地址(Address)或指针(Pointer)。地址从 0 开始依次增加,对于 32 位环境,程序能够使用的内存为 4GB,最小的地址为 0,最大的地址为 0XFFFFFFFF。

一切都是地址
数据和代码都以二进制的形式存储在内存中,计算机无法从格式上区分某块内存到底存储的是数据还是代码,当程序被加载到内存后,操作系统会给不同的内存指定不同的权限。代码是拥有读取和执行权限的内存块,数据是拥有读取和写入权限(也可能只有读取权限)的内存块。
CPU 访问内存时需要的是地址,而不是变量名和函数名!变量名和函数名只是地址的一种助记符,当源文件被编译和链接成可执行程序后,它们都会被替换成地址。编译和链接过程的一项重要任务就是找到这些名称所对应的地址。

指针就是地址,存储地址的变量称为指针变量。

//定义普通变量
float a = 99.5, b = 10.6;
char c = '@', d = '#';
//定义指针变量
float *p1 = &a;
char *p2 = &c;
//修改指针变量的值
p1 = &b;
p2 = &d;

定义指针变量时必须带  *  ,给指针变量赋值时不能带  *  。

p1,p2指向变化
在这里插入图片描述

指针连续定义:

int *p,*b,*c;   //定义了三个指向整型的指针变量p,b,c
int *p,b,c;     //定义了一个指向整型的指针变量p,定义了两个整型变量b,c;

在这里插入图片描述

指针不仅可以获取数据,还可以修改内存上的数据在这里插入图片描述

#include <iostream>
using namespace std;
int main()
{
    int *p,*p1,*p2,*p3,*p4;
    int a = 10,b = 50,c=100,e=200,f=260,g=400,h=500,i=600;
    cout<<"a的值:"<<a<<"  "<<"a的地址:"<<&a<<endl;
    cout<<"b的值:"<<b<<"  "<<"b的地址:"<<&b<<endl;
    cout<<"c的值:"<<c<<"  "<<"c的地址:"<<&c<<endl;
    cout<<"e的值:"<<e<<"  "<<"e的地址:"<<&e<<endl;
    cout<<"f的值:"<<f<<"  "<<"f的地址:"<<&f<<endl;
    cout<<"g的值:"<<g<<"  "<<"g的地址:"<<&g<<endl;
    cout<<"h的值:"<<h<<"  "<<"h的地址:"<<&h<<endl;
    cout<<"i的值:"<<i<<"  "<<"i的地址:"<<&i<<endl<<endl;;



    cout<<"1.定义*p并初始化 *p =&a: "<<endl;
    cout<<"a的值:"<<a<<"  "<<"a的地址:"<<&a<<endl;
    cout<<"*p的值:"<<*p<<"   "<<"*p的地址:"<<p<<endl<<endl;

    cout<<"2.改变指针变量p的指向  p=&b: "<<endl;
    p=&b;
    cout<<"a的值:"<<a<<"  "<<"a的地址:"<<&a<<endl;
    cout<<"*p的值:"<<*p<<"  "<<"*p的地址:"<<p<<endl<<endl;

    cout<<"3.改变变量c   c=(*p1)++ "<<endl;
    p1=&e;
    c=(*p1)++;
    cout<<"c的值:"<<c<<"  "<<"c的地址:"<<&c<<endl;
    cout<<"*p1的值:"<<*p1<<"  "<<"*p1的地址:"<<p1<<endl<<endl;

    cout<<"4.改变变量c   c=*p2++ "<<endl;
    p2=&f;
    c=*p2++;
    cout<<"c的值:"<<c<<"  "<<"c的地址:"<<&c<<endl;
    cout<<"*p2的值:"<<*p2<<"  "<<"*p2的地址:"<<p2<<endl<<endl;

    cout<<"5.改变变量c   c=++(*p3) "<<endl;
    p3=&g;
    c=++(*p3);
    cout<<"c的值:"<<c<<"  "<<"c的地址:"<<&c<<endl;
    cout<<"*p3的值:"<<*p3<<"  "<<"*p3的地址:"<<p3<<endl<<endl;
    cout<<"6.改变变量c     c=++*p4 "<<endl; /***     y = ++*px;  
                                                 px的内容加上1之后赋给y,
                                                  ++*px相当于++(*px)***    /
    p4=&h;
    c=++*p4;
    cout<<"c的值:"<<c<<"  "<<"c的地址:"<<&c<<endl;
    cout<<"*p4的值:"<<*p4<<"  "<<"*p4的地址:"<<p4<<endl<<endl;
    return 0;
}

/***
int  x , y , *px = &x,  *py = &y;
y = *px + 5;  //表示把x的内容加5并赋给y,*px+5相当于(*px)+5
py = px;  //把一个指针的值赋给另一个指针
***/
/***
变量内容的改变,但是地址不会发生变化
指针变量的指向改变,地址改变,内容改变,指针变量的地址改变,指针变量也会改变
在计算机中以栈的存储方式,FILO表
***/

在这里插入图片描述
++ 和 * 都是右结合性,由结合性可推导出:
后缀运算符++优先级高于前缀运算符++和 *
后缀++结合律从左至右(先返回值后自增)
前缀++和
优先级相同结合律从右至左
*
例如:
1: * p++与*(p++)相同,后缀++优先级更高,但后缀++先返回值(指针p),指针p与结合之后,指针p再++,因此对应的结果是,输出指针p在自增前对应的值,指针p自增。
2: ( * p)++ 括号优先级最高,因此先对指针p取值,然后后缀++先返回值
p,再对p这个整体自增,因此对应结果是输出p的值,之后*p的值自增1,指针p指向的位置不变。
3: * ++p 即 * (++p),最右是 * ,但后面跟的是表达式 ++p 所以要先算++p
4: ++ * p 即++( * p),最右是++ 但后面跟的是表达式 * p 所以要先算 * p

指向数组的指针:int ( * p)[ 4 ], 数组名是一个常量指针,指针p有四个元素
指针数组:int * a[ 10 ];10个int 型的指针

扫描二维码关注公众号,回复: 6044518 查看本文章
#include <iostream>
using namespace std;
int a[][3]={1,2,3,4,5,6,7,8,9};
int main()
{
    int (*pa)[3](a);   //定义指向数组a的指针
    for(int i=0;i<3;i++)
    {
        for(int j=0;j<3;j++)
        {
             cout<<*(*(pa+i)+j)<<" ";  //用指针指向数组中的元素
        }
        cout<<"\n";
    }
	return 0;
}

在这里插入图片描述

关于 * 和 & 的 理解:
假设有一个 int 类型的变量 a,pa 是指向它的指针。* &a可以理解为*(&a),&a表示取变量 a 的地址(等价于 pa),*(&a)表示取这个地址上的数据(等价于 pa),绕来绕去,又回到了原点,&a仍然等价于 a。

&*pa可以理解为&(*pa),*pa表示取得 pa 指向的数据(等价于 a),&(*pa)表示数据的地址(等价于 &a),所以&*pa等价于 pa。

对 * 的总结:
1、表示乘法
2、表示定义一个指针变量,用于和普通变量区分开
3、 表示取内容运算符,间接操作


不能对指针变量进行乘法、除法、取余等其他运算,除了会发生语法错误,也没有实际的含义


数组指针易错:

假设 p 是指向数组 arr 中第 n 个元素的指针:
*p++ 等价于 *(p++),表示先取得第 n 个元素的值,再将 p 指向下一个元素
*++p 等价于 *(++p),会先进行 ++p 运算,使得 p 的值增加,指向下一个元素,整体上相当于 *(p+1),所以会获得第 n+1 个数组元素的值。
(*p)++ 就非常简单了,会先取得第 n 个元素的值,再对该元素的值加 1。假设 p 指向第 0 个元素,并且第 0 个元素的值为 99,执行完该语句后,第 0 个元素的值就会变为 100。

c语言中有两种表示字符串的方法:一种是字符数组,字符数组存储在全局数据区域栈区,另一种是字符串常量,存储在常量区。全局数据区和栈区的字符串(也包括其他数据)有读取和写入的权限,而常量区的字符串(也包括其他数据)只有读取权限,没有写入权限。 它们在内存中的存储位置不同,使得字符数组可以读取和修改,而字符串常量只能读取不能修改。

指针函数:函数返回值是一个指针(地址)。函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数。函数返回的指针请尽量不要指向这些数据,C语言没有任何机制来保证这些数据会一直有效它们在后续使用过程中可能会引发运行时错误。

二级指针:指向指针的指针。

在这里插入图片描述
这是一个二维数组
str[ i ]返回str[ i ][ 0 ]的地址
str[ i ]+1返回str[ i ][ 1 ]的地址
str[ i ]+j返回str[ i ][ j ]的地址
应该不是读入一行,而是读入到" “或”\n"
一般要读入一个字符串char str[ n ];且输入数据没有空格或换行,也会写cin >> str;这样就直接输入一串。

int *(p1[5]);  //指针数组,可以去掉括号直接写作 int *p1[5];   
//   20个字节内存   ,每个元素都是指针
int (*p2)[5];  //二维数组指针,不能去掉括号。
//                   4个字节内存
小结:

指针(Pointer)就是内存的地址,C语言允许用一个变量来存放指针,这种变量称为指针变量。
指针变量可以存放基本类型数据的地址,也可以存放数组、函数以及其他指针变量的地址。
程序在运行过程中需要的是数据和指令的地址,变量名、函数名、字符串名和数组名在本质上
是一样的,它们都是地址的助记符:在编写代码的过程中,我们认为变量名表示的是数据本身,
而函数名、字符串名和数组名表示的是代码块或数据块的首地址;程序被编译和链接后,这些
名字都会消失,取而代之的是它们对应的地址。

常见指针变量的定义
int *p : p 可以指向 int 类型的数据,也可以指向类似 int arr[n] 的数组。
int **p : p 为二级指针,指向 int * 类型的数据。
int *p[n] : p 为指针数组。[ ] 的优先级高于 *,所以应该理解为 int *(p[n]);
int (*p)[n] : p 为二维数组指针。
int *p() : p 是一个函数,它的返回值类型为 int *。
int (*p)() : p 是一个函数指针,指向原型为 int func() 的函数。

1) 指针变量可以进行加减运算,指针变量的加减运算并不是简单的加上或减去一个整数,
而是跟指针指向的数据类型有关。
2) 给指针变量赋值时,要将一份数据的地址赋给它,不能直接赋给一个整数,例如
int *p = 50;是没有意义的,使用过程中一般会导致程序崩溃。
3) 使用指针变量之前一定要初始化,否则就不能确定指针指向哪里,如果它指向的
内存没有使用权限,程序就崩溃了。对于暂时没有指向的指针,建议赋值NULL。
4) 两个指针变量可以相减。如果两个指针变量指向同一个数组中的某个元素,那么
相减的结果就是两个指针之间相差的元素个数。
5) 数组也是有类型的,数组名的本意是表示一组类型相同的数据。在定义数组时,
或者和 sizeof、& 运算符一起使用时数组名才表示整个数组,表达式中的数组名会
被转换为一个指向数组的指针。

https://blog.csdn.net/cherish_2012/article/details/42091473

悬垂指针:

定义:指向曾经存在的对象,但该对象已经不再存在。结果未定义,往往导致程序错误,而且难以检测。
避免方法:
引入智能指针可以防止垂悬指针出现。一般是把指针封装到一个称之为智能指针类中,这个类中另外
还封装了一个使用计数器,对指针的复制等操作将导致该计数器的值加1,对指针的delete操作则会
减1,值为0时,指针为NULL

哑指针:

哑指针指传统的C/C++指针,它只是一个指向,除此以外它不会有其他任何动作,
所有的细节必须程序员来处理,比如指针初始化,释放等等

智能指针:

https://blog.csdn.net/flowing_wind/article/details/81301001

野指针:

         野指针,也就是指向不可用内存区域的指针。如果对野指针进行操作,将会使程序发生不可预知的错
误, 甚至可能直接引起崩溃。
         野指针不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易
判断。但是野指针是很危险的,也具有很强的掩蔽性,if语句对它不起作用。
       造成野指针的常见原因有三种:
        1、指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针。在Debug模式下,
VC++编译器会把未初始化的栈内存上的指针全部填成 0xcccccccc ,当字符串看就是 “烫烫烫烫……”;
会把未初始化的堆内存上的指针全部填成 0xcdcdcdcd,当字符串看就是 “屯屯屯屯……”。把未初始化
的指针自动初始化为0xcccccccc或0xcdcdcdcd,而不是就让取随机值,那是为了方便我们调试程序,
使我们能够一眼就能确定我们使用了未初始化的野指针。在Release模式下,编译器则会将指针赋随机
值,它会乱指一气。所以,指针变量在创建时应当被初始化,要么将其设置为NULL,要么让它指向合
法的内存。
        2、指针指向的内存被释放了,而指针本身没有置NULL。对于堆内存操作,我们分配了一些空间
(使用malloc函数、calloc函数或new操作符),使用完后释放(使用free函数或delete操作符)。指
针指向的内存被释放了,而指针本身没有置NULL。通常会用语句if (p != NULL)进行防错处理。很遗
憾,此时if语句起不到防错作用。因为即便p不是NULL指针,它也不指向合法的内存块。所以在指针
指向的内存被释放后,应该将指针置为NULL。
        3 、指针超过了变量的作用范围。即在变量的作用范围之外使用了指向变量地址的指针。这一般
发生在将调用函数中的局部变量的地址传出来引起的。这点容易被忽略,虽然代码是很可能可以执行
无误,然而却是极其危险的。局部变量的作用范围虽然已经结束,内存已经被释放,然而地址值仍是
可用的,不过随时都可能被内存管理分配给其他变量。
C++标准规定:delete空指针是合法的,在delete 指针后赋值为NULL,若没有赋值为NULL,再次
delete会报错,不能运行。因为delete是释放指针指向的内存,并不是指针所占有的内存。所以
delete后,指针还是指向那块区域,并没有清0,所以下次使用会发生XXX空间不能访问正常*斜体样式*

猜你喜欢

转载自blog.csdn.net/weixin_43517282/article/details/89048472
今日推荐