C++复习专用-指针专题

指针

在学习指针时,首先要知道()的优先级高于 [] , 且 [] 的优先级高于*

明确这一点,可以对包含指针的语句分析时,抽丝剥茧,快速理解复杂的语句。

成员访问的方法

A.B

A为对象或者结构体,访问A的成员B,若A是指针则不适用
A->B A为指针,->是成员提取,A->B是提取A中的成员B,且A只能是指向类,结构的指针
:: 作用域运算符,A::B表示作用域A中的名称B, A是命名空间,类,结构
: 表示继承
优先级(高到低)
:: 作用域解析
()  
[] 数组
-> 成员访问
++ 后缀自增
--  
!
~ 位非
+ 正,加
-  
++ 前缀自增
--  
& 地址
* 接触引用(指针)
   
  • 指针是一个变量,存储的是地址信息
    • P的值是地址
    • *P的值是指向的数据值,完全可以把*P看成一个普通变量
  • 获取地址值&
    • 对于普通变量,想要获得该变量的地址,需要用地址运算符&,即&a表示的就是变量a的地址,和P一个意思
  • *运算符也被称为是解除引用运算符
  • 声明指针
    • 指针的声明必须,指定指向的数据类型,如 int * p;
      • 这里的*p 的数据类型是int,
      • p是指向int类型的指针
    • int *p 强调*p是个int类型,把 *p 看成普通int类型变量
    • int* p强调int*是一种类型,即p是指向int类型的指针,强调的是int* ,把int*看成一种数据类型
double * p;    //定义声明了一个指向double类型的指针p
double* p;    //定义声明了一个指向double类型的指针p,强调p是指向double类型的指针变量
double *p;    //定义声明了一个指向double类型的指针p,强调*p是double类型
  • 指针初始化
    • Question: 初始化的是指针地址还是指针指向的数据值?
      • 初始化的是指针存储的地址
    • int *p = &a ;
  • 指针可以用关系运算符进行比较,如 ==、< 和 >。如果 p1 和 p2 指向两个相关的变量,比如同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较。
  • &运算符注意点
    • 对数组名进行&操作时,得到的是整个数组的地址,而不是第一个元素的地址(数组名=第一个元素的地址,无需&运算符),虽然这两个地址是相同的,但是操作起来时会有明显差异,如下:
    • &array[2]和&array得到的是一个2字节的内存块的地址值,而&array则是一个20个字节内存块的首地址
  • 存储
    • 自动存储
      • 在函数内部定义的常规变量使用自动存储空间,这些变量也叫自动变量,其使用的存储空间成为栈
      • 自动变量其实是局部变量,随函数调用诞生,结束消亡
      • 栈即LIFO后入先出
    • 静态存储
      • 程序执行期间都存在的存储方式,可以通过函数体外定义它,也可以通过声明变量时使用关键字static
      • 只要程序活着,静态变量就一直存在
    • 动态存储
      • 运算符new和delete管理一个内存池,相当于自动存储的栈,但这里叫做堆,栈和堆事独立分开的
      • 动态存储在程序运行时按需分配内存,用完就删除
  • 模版类vector
    • vector也是一种动态数组,同样是new和delete管理内存的,但vector是自动完成的
    • vector<typeName> name(number); 
    • vector<typeName> name;
  • 指针和运算符
    • 指针算术上述已有所述,这里结合自增运算符
    • *++p,这里表示先把p的地址值增加1个单位的字节数,再结合*解除运算符
    • *p++,同理
    • ++*p,这里是把*p直接看成普通变量,再结合++自增运算符
    • (*p)++ 这里是把*p看成普通变量

指针 vs 数组

指向数组的指针

牢记下边两个恒等式 arr为数组名,p为数组名地址值赋值的指针, p=arr

左边是数组表示法,右边是指针表示法

arr[i]  == *(p+i)

&arr[i] == p + i

  • 一个指向数组开头的指针,可以通过使用指针的算术运算或数组索引来访问数组(就是上述两个式子)
    • 指针和数组并不是完全互换的,数组名相当于一个指针常量,其值为数组0号元素的首地址。
    • 不可改变数组名的值,但指针变量可以进行运算
    • 指针运算符 * 应用到 数组arr上是完全可以的,但修改arr存储的值是非法的,只能修改var代表的数组的数组成员的值。
    • 例外:sizeof运算符对数组名取长度,返回的是整个数组长度
    #include <iostream>
    
    using namespace std;
    const int MAX = 3;
    
    int main ()
    {
       int  var[MAX] = {10, 100, 200};
       int  *ptr;
       ptr = var;
        
       for (int i = 0; i < MAX; i++)
       {
          *var = i;    // 这是正确的语法
          var++;       // 这是不正确的
          ptr++;       //TRUE, 指针加1个int单位字节,从而指向了下一个元素
       }
       return 0;
    }
  • 数组名等于第0个元素的首地址
    • arr和&arr[0]相同
    • arr和&arr虽然值相同,但是arr是第0个元素首地址,而&arr是整个数组首地址
      • 当赋给指针进行算术运算时,前者将增减一个元素内存字节大小,而后者将增减一个数组字节大小
  • 创建一个指向数组的指针
    • 下边代码中:
      • pa是个指针,指向数组0元素首地址
      • pb是个指针,和pa相同
      • pbb是个指针,与pa,pb同值,但pbb指向的是整个数组
      • pc是个指针,指向一个包含了100个double的数组
      • pd是个数组,该数组包含了100个double*指针元素
double arr[100];
double *pa = arr;
double *pb = &arr[0];
double *pbb = &arr;

double (*pc)[100];
double *pd[100];

new运算符分配内存

  • 在C语言中,使用库函数malloc()来分配内存,在C++中也兼容,但是推荐使用new运算符来分配内存
  • new运算符的操作流程:
    • ①在运行阶段为一个int值即int类型的数据,该数据没有名字,即无内存标签,为这个数据来分配未命名的内存(这个int类型数据是占内存空间的,只不过没有为这个内存空间打标签)
    • ②分配未命名的内存,同时利用指针访问这个内存块
    • ③我只需要告诉new,我需要创建一个什么类型的数据即可
    • ④new运算符会自动找到一个合适的内存块,并返回内存块的地址
    • ⑤总而言之,我只需要告诉new数据类型即可,new会把一个对应的内存块地址交给一个指针
      • int * ptr = new int;
      • 前后两个数据类型要对应,这里是int
  • Q:指针初始化和new分配内存有什么区别呢?
    • int * p = &a; 指针初始化,可以通过非指针变量来访问数据,即通过a来修改内存块中的数值,也可以通过*p来修改
    • int * ptr = new int; new分配内存只能通过*ptr来修改访问数据
  • Q:对地址你了解多少呢?
    • 地址是内存块的名字,变量则是内存块的标签
    • 注意:地址只是内存块的第一个bit的名,比如x01010101,如果是int类型,那么加32就是该内存块的最后一个bit的地址名
  • new运算符来创建动态数组
    • 当数据量很大时,应避免使用常规数组,字符串,结构等,应尽量使用new运算符来创建动态数组
    • 场景: 当你写程序时,发现不确定是否使用数组,那么用不用数组全看输入什么数据,这时候就需要了解一下静态联编和动态联编的问题
      • 静态联编: 在编译时分配内存,不管程序是否调用数组,都会占据相应的内存空间
      • 动态联编: 在编译时不分配内存,即在运行时根据程序是否调用而创建相应大小的内存空间,创建的长度和实际长度相同,这种特殊的数组叫做dynamic array动态数组\
    • int *ptr_dynamic_array = new int[11];
      • []表示元素个数
      • new返回数组第一个元素的地址
    • 使用完毕时,要使用delete运算符释放数组,直接delete ptr_dynamic_array
    • 动态数组的使用,只需要把指针当作数组名来用即可,比如ptr_dynamic_array[10]
  • new运算符的补充
    • 通过new运算符可以创建动态数组,也可以创建动态结构和动态类,原理和new创建动态数组一样,即在运行时按需分配内存块,返回内存块首地址
    • inflatable * ps = new inflatable;
      • *ps是个inflatable结构类型的指针,inflatable*和int*一个道理
    • 这里补充一下,如果你的指针是个结构,那么成员的访问就需要用成员运算符->来访问

指针算术

  • 指针+1,这里+1表示增加1个指针指向类型的内存字节数,比如int * p; p+1表示p存的地址值增加8个字节
  • 数组,地址,指针
    short tell[10];
    cout<<tell<<endl;
    cout<<&tell<endl;
    cout<<&tell[0]<<endl;
    • 变量指针可以递增数组不能递增数组可以视为一个常量指针。
    • 数组名是其第一个元素的地址值,指针变量存放的也是地址值,所以指针变量可以当作数组名使用。
    • 对数组名进行&取地址,需要注意:
      • 对数组名取地址,&tell得到的是整个数组的地址,即得到的是一个数组所占内存块的块地址,&tell
        • 对数组名取地址后,&tell 所可以赋值的指针,则是指向了一个包含了10个元素的数组
        • 将&tell + 2那么,地址必然增加的是20
      • 对数组第一个元素取地址,&tell[0] 就是一个元素所占内存块的块地址,往往默认为是数组名tell
        • 把数组名tell看成指针,由于tell就是&tell[0],那么tell就是一个指向short类型的指针常量,
        • 将tell+1,那么地址增加2个字节
    • 因此对于数组和指针,既可以使用数组表示法,也可以使用指针表示法。
int main(){
    double * ptr = new double;
    ptr[1] = 100.21;
    cout<<*(ptr+1)<<endl;
    return 1;
}

// 100.21
#include <iostream>

using namespace std;
const int MAX = 3;

int main ()
{
   int  var[MAX] = {10, 100, 200};
   int  *ptr;

   // 指针中最后一个元素的地址
   ptr = &var[MAX-1];
   for (int i = MAX; i > 0; i--)
   {
      cout << "Address of var[" << i << "] = ";
      cout << ptr << endl;

      cout << "Value of var[" << i << "] = ";
      cout << *ptr << endl;

      // 移动到下一个位置
      ptr--;
   }
   return 0;
}
Address of var[3] = 0xbfdb70f8
Value of var[3] = 200
Address of var[2] = 0xbfdb70f4
Value of var[2] = 100
Address of var[1] = 0xbfdb70f0
Value of var[1] = 10



int *pt = new int [10];
*pt = 5;
pt[0] = 6;
pt[9] = 44;

int coats [10];
* (coats +4) = 99;
//解释:
//coats表示数组第一个元素地址,+4则地址增加4个int内存空间,即移动到coats[4]的位置
//*(coats+4) 和 coats[4] 等价

辨析:

short (*pas)[20];
short *pas[20];

int *ar2[4];
int (*ar2)[4];

//解析
//牢记优先级() [] *
short (*pas)[20]; //pas是指针,指向由20个short元素组成的数组的指针

short *pas[20];  //pas是个数组,包含了20个指向short类型指针的数组

int *ar2 [4]; //ar2是个数组,表示由4个int指针组成的数组
//-1-参考int arr[4];表示arr是个int数组,长度为4
//-2-int* ar2[4];表示ar2是个int*数组,长度为4,只不过元素都是只想int的指针

int (*ar2) [4];//ar2是个指针,指向一个包含了4个int的数组

//-1-先看括号里面,(*ar2),表示ar2是个指针
//-2-再看括号外面,int ()[4];表示名为()的int数组,长度为4
  •  

指针和const

  • const表示常量,对于指针而言,有两个目标,一个是指针存的地址值,另一个是指针指向的变量值
  • 直接举栗子说明
// NO.1 right
int age = 30;
const int * p = &age;  //无法通过p修改age,age自身可修改

// NO.2 right
const int age = 30;
const int * ptr = &age;    //无法通过ptr修改age,age自身不可修改

// NO.3 false
const int age = 30;
int *ptr = &age;     //可通过ptr修改age,age自身不可修改,矛盾,ERROR


  • 接下来辨析如下四种类型,主要是指针常量, 指针变量,和常量,变量之间的关系辨析
  • int类型的实体不能用const int来初始化,反之可以
#include <iostream>

using namespace std;

int main() {
	/*当对象是变量或者常量时,思考const的存在或不同位置,能否导致指针自身和指针指向的对象 改变?*/

	int num = 10;  //当对象是int变量
	int test = 1;
	int * num_ptr0 = &num;					   //  TRUE, *num_ptr0,可修改,num_ptr0可修改,num可修改
	const int * num_ptr1 = &num;			   //  TRUE, *num_ptr1常量不可修改,num_ptr1可修改,num可修改
	int * const num_ptr2 = &num;			   //  TRUE, *num_ptr2可以修改, num_ptr2不可修改,num可修改
	const int* const num_ptr3 = &num;    //   TRUE, *num_ptr3常量不可修改, num_ptr3常量不可修改,num可修改


	const int price = 100; //当对象是int常量
	
	int * price_ptr0 = &price;                     //ERROR, 指针指向类型和指向对象类型不匹配, nt类型的实体不能用const int来初始化,反之可以
	const int *price_ptr1 = &price;             //TRUE, 
	int * const price_ptr2 = &price;			   //ERROR, 指针指向类型和指向对象类型不匹配, int类型的实体不能用const int来初始化,反之可以
	const int* const price_ptr3 = &price;    //TRUE

	return 0;
}
  • 在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为指针。
  • 如需检查一个空指针,您可以使用 if 语句,
#include <iostream>

using namespace std;

int main ()
{
   int  *ptr = NULL;

   cout << "ptr 的值是 " << ptr ;

   return 0;
}

ptr 的值是 0

if(ptr)     /* 如果 ptr 非空,则完成 */
if(!ptr)    /* 如果 ptr 为空,则完成 */

函数指针

指向函数的指针

  • 获取函数的地址
    • 直接使用函数名(注意区分函数名和函数返回值),如下,think为函数名,think()为函数返回值。
process(think);
process(think());
  • 声明函数指针与初始化赋值
    • 牢记优先级() []  *  的顺序为从高到低
    • 需指定函数的返回类型、特征标(直接将函数声明的函数名用指针替换即可,如下)
double pam(int);
double (*f)(int);

f = pam;
  • 函数指针的调用
    • 直接把(*f)或者f作为函数名pam来用,都可以,注意(*f)的括号()
double pam(int);
double (*f)(int);
f = pam;

double x = pam(4);
double y = (*f)(4);
double z = f(4);
  • 辨析
double pam(int);
double *f1(int);
double (*f2)(int);


int arr[10];
int *p = &arr[0];
int *a = arr;
int *b = &arr;
int *c[10] = &arr;
int (*d)[10] = &arr;


上述代码中:

f1是个函数,返回值类型是个double* 指针

f2是个指针,指向了一个函数,类型就是double (*)(int);

p是个指针,指向数组arr的0号元素首地址

a是个指针,和p一样(数组名arr就是0号元素首地址)

b是个指针,虽然和a一样的值,但指向的是整个数组内存块的首地址,当b进行+1运算时,会增加整个数组内存字节数大小

c是个数组,包含了10个int* 指针元素的数组

d是个指针,指向了一个包含10个int元素的数组

深入学习函数指针

函数原型

在函数原型中,特征标即参数列表有如下规则:

  • arr[] 和 *arr 等价
  • 可以省略标识符 const double ar[] 简化为 const double [],  const double *arr 同理const double *
    • 函数定义时不可以省略!

包含函数指针的数组

const double * (*pa[3])(const double *,int) = {f1,f2,f3};

语句书写思路:先写出返回类型和特征标 const double * ()(const double*, int) 再向()内填写一个包含指针的数组表达式*pa[3]

指向包含函数指针数组的指针

const double * (*(*pa)[3])(const double *, int) = &pa;

语句书写思路:先写出返回类型和特征标const double * ()(const double *, int)再向()内追加 一个 指向包含指针元素的数组的 指针表达式*(*pa)[3]

猜你喜欢

转载自blog.csdn.net/Mrsherlock_/article/details/109167479