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语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
56 0
|
20天前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
73 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
20天前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
45 9
|
20天前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
40 7
|
1月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
105 13
|
24天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
24天前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
80 3
|
25天前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
1月前
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
52 11
|
24天前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
35 1