18.【彻底改变你对C语言指针的厌恶(超详细)】

简介: 18.【彻底改变你对C语言指针的厌恶(超详细)】

(一)、复杂类型说明:

要了解指针,首先我们要先了解一下复杂的类型,指针和普通的表达式一样也有优先级,我们只需要对优先级顺序把握清楚就能把各种各样的指针类型搞明白,指针也就明白了.

#include <iostream>
using namespace std;
int main()
{
  int p;     //首先p是普通的整数类型
  int* p;     //因为*优先级高,所以我么先看*,*是指针然后int ,所以p是一个指向整形的指针变量.
  int p[3];   //【】优先级高,所以先看数组,然后整形,这是一个整型数组
  int* p[3];    //  []优先级高,所以顺序为:数组、指针、整形、这是一个指向整型的数组指针.
  int(*p)[3];  //  ()优先级高于[]所以先()、[]、整形、这是一个指向整型的指针数组变量.
  int** p;    //先于一个*结合,整形指针,然后再与一个*,结合,说明指针指向的元素是指针,这是一个指向整形指针元素的指针
  int p(int);   //()优先级高、()/int,说明这是一个返回类型为整数的函数
  int (*p)(int);  //当优先级同等的时候,从左向右看齐,所以指针、函数、类型、这是一个指向整型,返回值为整形的指针变量.
}

说到这大家应该对指针也有初步了解了吧?

(二)、什么是指针?

指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。要搞清楚一个指针,需要搞清楚指针的四方面内容:

指针的类型、指针所指向的类型、指针的值或则叫做指针所指向的值、指针本身所占据的内存.

举列子1.

#include <iostream>
using namespace std;
int main()
{
  int* p;     //因为*优先级高,所以我么先看*,*是指针然后int ,所以p是一个指向整形的指针变量.
  int* p[3];    //  []优先级高,所以顺序为:数组、指针、整形、这是一个指向整型的数组指针.
  int(*p)[3];  //  ()优先级高于[]所以先()、[]、整形、这是一个指向整型的指针数组变量.
  int** p;    //先于一个*结合,整形指针,然后再与一个*,结合,说明指针指向的元素是指针,这是一个指向整形指针元素的指针
  int (*p)(int);  //当优先级同等的时候,从左向右看齐,所以指针、函数、类型、这是一个指向整型,返回值为整形的指针变量.
}

2.1、指针的类型:

指针的类型简单理解就是把指针的变量给去掉:

#include <iostream>
using namespace std;
int main()
{
  int* p;       // 去掉p,指针类型是int*
  int* p[3];    // 去掉p,指针类型是int *[]
  int(*p)[3];  //  去掉p,指针类型是int (*)[]
  int** p;    //去掉p,指针类型是int **
  int (*p)(int);  //去掉p,指针类型是int (*)(int)
}

2.2、指针所指向的类型:

!](https://ucc.alicdn.com/images/user-upload-01/4a1032746c3e4702a23a4ca3a687cc08.png#pic_center)

只需要把指针声明的名字和去掉就是.*

#include <iostream>
using namespace std;
int main()
{
  int* p;       // 去掉*p,指针类型是int
  int* p[3];    // 去掉*p,指针类型是int []
  int(*p)[3];  //  去掉*p,指针类型是int ()[]
  int** p;    //去掉*p,指针类型是int *
  int (*p)(int);  //去掉*p,指针类型是int ()(int)
}

2.3、指针的值(指针所指向的内存或地址):

指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32 位程序里,所有类型的指针的值都是一个32 位整数,因为32 位程序里内存地址全都是32 位长。

指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为si zeof(指针所指向的类型)的一片内存区。

以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。

以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?(重点注意)

2.4、指针本身占据的空间

指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32 位平台里,指针本身占据了4 个字节的长度。指针本身占据的内存这个概念在判断一个指针表达式(后面会解释)是否是左值时很有用。

(三)、指针的运算符&和*

&是取地址运算符,是间接运算符.
&a的运算结果是一个指针,指针的类型是a的类型加一个
*,指针所指向的类型是a的类型,指针所指向的地址是a的地址。

*p的运算结果就五花八门了,*p的结果就是p所指向的东西,它指向的类型是p指向的类型,他所占用的地址是p指向的地址.

#include <iostream>
using namespace std;
int main()
{
  int a = 12, b;
  int* p;        
  int** per;    //类型是int**,指向的类型是: int*
  p = &a;  //&a的结果是一个指针。a的指针类型是int*、指向的类型是int、指向的地址是a,p的类型是int*
  //*p=&a;   会报错因为*p的类型是int 而&a的类型int*会报错     //cout << *p << endl;结果为12       cout << p  << endl; 一个a地址
  *p = 24;   //*p的结果在这里它的类型是int、他所占用的地址是p所指向的地址,
  *per = &a;  //这里*per的类型是int*,&a的类型也是int*
  //*per = 24;  //会报错因为*per是int*,24是int
  **per = 34;   // **per在这里是int 、34是int.
}

(四)、指针的表达式

定义:一个表达式的结果如果是一个指针,那么这个表达式就叫做指针表达式。

#include <iostream>
using namespace std;
int main()
{
  int a,b,arry[10];
  int* p;
  int** per;
  p = &a;   //&a是一个表达式
  //*per = &p;   //这里p的类型是int**,*per式int*
  *per = &b;   //&b和*per都是一个指针表达式.
  int** per = &p;  //这里&p是一个指针表达式
  p = arry;
  p++;     //p++也是一个指针表达式
}

由于指针表达式的结果是一个指针,所以指针表达式也具有指针所具有的四个要素:指针的类型,指针所指向的类型,指针指向的内存区,指针自身占据的内存。

好了,当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话,这个指针表达式就是一个左值,否则就不是一个左值。在例子中,&a 不是一个左值,因为它还没有占据明确的内存。ptr 是一个左值,因为ptr 这个指针已经占据了内存,其实ptr 就是指针pa,既然pa 已经在内存中有了自己的位置,那么ptr 当然也有了自己的位置。

(五)、数组和指针的关系:

数组的数组名其实可以看作一个指针(&数组名)

#include <iostream>
using namespace std;
int main()
{
  int a, b, arry[10] = {1.2,3,4.5};
  *arry;         //arry的指针类型是int*,再加一个*,指针类型就变成指向int.
  *(arry + 1);
  cout << *arry << " " << *(arry + 1) < " ";
}

上例中,一般而言数组名array 代表数组本身,类型是int[10],但如果把array 看做指针的话,它指向数组的第0 个单元,类型是int 所指向的类型是数组单元的类型即int*。因此array 等于0 就一点也不奇怪了。同理,array+3 是一个指向数组第3 个单元的指针,所以(array+3)等于4。其它依此类推。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
int main()
{
  const char* str[3] = {"hello","world","!"};
  char s[80];
  strcpy(s, *str);
  cout << s << endl;
  strcpy(s, *(str+1));
  cout << s << endl;
  strcpy(s, *(str + 2));
  cout << s << endl;
}

下面总结一下数组的数组名(数组中储存的也是数组)的问题:

声明了一个数组TYPE array[n],则数组名称array 就有了两重含义:

第一,它代表整个数组,它的类型是TYPE[n]
第二,它是一个常量指针,该指针的类型是TYPE
,该指针指向的类型是TYPE
也就是数组单元的类型,该指针指向的内存区就是数组第0 号单元,该指针自己占有单独的内存区,注意它和数组第0 号单元占据的内存区是不同的。该指针的值是不能修改的,即类似array++的表达式是错误的在不同的表达式中数组名array 可以扮演不同的角色。在表达式sizeof(array)中,数组名array 代表数组本身,故这时sizeof 函数测出的是整个数组的大小。
在表达式
array 中,array 扮演的是指针,因此这个表达式的结果就是数组第0 号单元的值。sizeof(*array)测出的是数组单元的大小。

表达式array+n(其中n=0,1,2,…)中,array 扮演的是指针,故array+n 的结果是一个指针,它的类型是TYPE *,它指向的类型是TYPE,它指向数组第n号单元。故sizeof(array+n)测出的是指针类型的大小。在32 位程序中结果是4.

*(六)、指针的算术问题

指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的,以单元为单位。例如:

有点不好理解,但不要放弃!!!!!!

前置++ > * > 后置++ > *()++

#include <iostream>
using namespace std;
int main()
{   //优先级为左++ *  右++
  int* p;
  int a[30] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 };
  p = a;
  cout << *p<<" "<<endl;
  cout << *++p <<" ";
  cout << *p << " " << endl;
  cout << *p++ <<" ";
  cout << *p << " " << endl;
  cout << *(p)++ <<"  ";
  cout << *p << " " << endl;
  cout<< *p++ <<"   " <<* (p)++ << endl;
  cout << *p <<" "<< * ++p <<"  "<< endl;
  cout << *p <<"  "<< * p++ << endl;
  cout << *p<<"  "<< * (p++) <<"  " << endl;
  cout << *p << "  " << *++p << "   " << *p++ << endl;
  cout << *p << "  " << *++p << "   " << *p++ << "   " << *(p)++ << endl;
  cout << *++p <<"  " << *p++ << endl;
  cout<<*++p<<"   "<< * (p)++ << endl;
  cout<< *p++ <<"   "<< * (p)++ << endl;
  cout << * ++p << "   "<< * p++ <<"   "<< * (p)++ << endl;
}

*注意事项: §++ 是单纯的指数值+1,而不是元素:

有点不好理解,但不要放弃!!!!!

#include <iostream>
using namespace std;
int main()
{   //优先级为左++ *  右++
 int* p;
 int a[30] = { 1};
 p = a;
 cout << *p<<" "<<endl;
 cout << (*p)++ << endl;           //单纯的+1
 cout << *p << " " << endl;
 cout << *++p <<" ";
 cout << *p << " " << endl;
 cout << *p++ <<" ";
 cout << *p << " " << endl;
 cout << *(p)++ <<"  ";
 cout << *p << " " << endl;
 cout<< *p++ <<"   " <<* (p)++ << endl;
 cout << *p <<" "<< * ++p <<"  "<< endl;
 cout << *p <<"  "<< * p++ << endl;
 cout << *p<<"  "<< * (p++) <<"  " << endl;
 cout << *p << "  " << *++p << "   " << *p++ << endl;
 cout << *p << "  " << *++p << "   " << *p++ << "   " << *(p)++ << endl;
 cout << *++p <<"  " << *p++ << endl;
 cout<<*++p<<"   "<< * (p)++ << endl;
 cout<< *p++ <<"   "<< * (p)++ << endl;
 cout << * ++p << "   "<< * p++ <<"   "<< * (p)++ << endl;
}

总结一下

一个指针ptrold 加(减)一个整数n 后,结果是一个新的指针ptrnew,ptrnew 的类型和ptrold 的类型相同,ptrnew 所指向的类型和ptrold所指向的类型也相同。ptrnew 的值将比ptrold 的值增加(减少)了n 乘sizeof(ptrold 所指向的类型)个字节。就是说,ptrnew 所指向的内存区将比ptrold 所指向的内存区向高(低)地址方向移动了n 乘sizeof(ptrold 所指向的类型)个字节。指针和指针进行加减:两个指针不能进行加法运算,这是非法操作,因为进行加法后,得到的结果指向一个不知所向的地方,而且毫无意义。两个指针可以进行减法操作,但必须类型相同,一般用在数组方面,不多说了。

(七)、指针和结构类型的关系

enter)

可以声明一个指向结构体对象的指针

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
struct mystruct
{
  int a;
  int b;
  int c;
};
int main()
{
  struct mystruct ss = { 20,30,40 };//声明结构体对象ss,并赋值
  struct mystruct* p = &ss;  //声明一个指向结构体对象,类型是mystruct*,指向mystruct的对象.
  p = &ss;   
  int* pstr = (int*)&ss;
  cout << p->a << " ";          //访问成员变量a
  cout << p->b << " ";          //访问成员变量b
  cout << p->c << " ";          //访问成员变量c
  cout<<*pstr<<" ";             //访问成员变量a
  cout << *(pstr+1) << " ";    //访问成员变量b
  cout << *(pstr+2) << " ";    //访问成员变量b
}

请问怎么通过p访问结构体的成员变量呢?

cout << p->a << " ";          //访问成员变量a
  cout << p->b << " ";          //访问成员变量b
  cout << p->c << " ";          //访问成员变量c

请问怎么通过pstr访问结构体的成员变量呢?

cout<<*pstr<<" ";             //访问成员变量a
  cout << *(pstr+1) << " ";    //访问成员变量b
  cout << *(pstr+2) << " ";    //访问成员变量b

(八)、指针和函数的关系

可以把一个指针声明成为一个指向函数的指针,可以把指针作为形参,在函数调用语句中,可以用指针表达式来作为实参.

(九)、指针与常量

const 可以修饰一般变量,这样的变量我们称之为常变量,常变量的值是不可以修改的.const 也可以修改指针变量指向常量的指针变量:用来指定指针变量是一个常量,或则指定指针变量指向的对象是一个常量.

const 类型名*p 指针变量名;

#include <iostream>
using namespace std;
int main()
{
 int a = 10;
 const int * p = &a;
 //*p = a;     会报错,表达式必须是了修改的左值;
 p = &a;
 a = 20;
 cout << a << endl;
}

分析:用指向常量的指针变量只是限制了通过指针变量改变它指向的对象的值,但是可以通过它本身来改变它的值.

常指针(常地址):

类型名 *const 指针变量名;

#include <iostream>
using namespace std;
int main()
{
 int a = 10;
  int *const p = &a;
 *p = a;       
 //p = &a;        会报错,表达式必须是了修改的左值;
 a = 20;
 cout << a << endl;
}

分析:常指针变量的地址不能变,必须定义时初始化,指针变量的指向不能改变。但是指针变量的指向变量的知识可以修改的.

制作不易还请多多支持!!!!!!!

(十)数组指针和指针数组:

因为篇幅过长不宜展示:特加链接在此

数组指针和指针数组(点击!!!!)

完结!!!!!!!! 如有不解,此博主支持私聊.


相关文章
|
2天前
|
存储 C语言
【C语言篇】深入理解指针3(附转移表源码)
【C语言篇】深入理解指针3(附转移表源码)
11 1
|
2天前
|
存储 程序员 编译器
【C语言】指针篇-简单快速了解指针-必读指南(1/5)
【C语言】指针篇-简单快速了解指针-必读指南(1/5)
|
1天前
|
存储 C语言
深入浅出C语言指针(基础篇)
深入浅出C语言指针(基础篇)
|
2天前
|
算法 搜索推荐 C语言
【C语言篇】深入理解指针4(模拟实现qsort函数)
【C语言篇】深入理解指针4(模拟实现qsort函数)
10 2
|
2天前
|
C语言 C++
【C语言】指针篇-一篇搞定不同类型指针变量-必读指南(3/5)
【C语言】指针篇-一篇搞定不同类型指针变量-必读指南(3/5)
|
28天前
|
存储 C语言
【C语言基础】一篇文章搞懂指针的基本使用
本文介绍了指针的概念及其在编程中的应用。指针本质上是内存地址,通过指针变量存储并间接访问内存中的值。定义指针变量的基本格式为 `基类型 *指针变量名`。取地址操作符`&`用于获取变量地址,取值操作符`*`用于获取地址对应的数据。指针的应用场景包括传递变量地址以实现在函数间修改值,以及通过对指针进行偏移来访问数组元素等。此外,还介绍了如何使用`malloc`动态申请堆内存,并需手动释放。
|
1天前
|
存储 C语言 C++
深入浅出C语言指针(进阶篇)
深入浅出C语言指针(进阶篇)
|
2天前
|
Serverless 编译器 C语言
【C语言】指针篇- 深度解析Sizeof和Strlen:热门面试题探究(5/5)
【C语言】指针篇- 深度解析Sizeof和Strlen:热门面试题探究(5/5)
|
2天前
|
搜索推荐 C语言 C++
【C语言】指针篇-精通库中的快速排序算法:巧妙掌握技巧(4/5)
【C语言】指针篇-精通库中的快速排序算法:巧妙掌握技巧(4/5)
|
2天前
|
编译器 C语言
【C语言】指针篇-深入探索数组名和指针数组- 必读指南(2/5)
【C语言】指针篇-深入探索数组名和指针数组- 必读指南(2/5)