指针的用法和注意事项(二)

指针及其大小、用法

指针的定义:

指针是一种变量类型,其值为另一个变量的地址,即内存位置的直接地址。就像其他变量或常量一样,必须在使用指针存储其他变量地址之前,对其进行声明。在 64 位计算机中,指针占 8 个字节空间。

使用指针时可以用以下几个操作:

  • 定义一个指针变量;
  • 把变量地址赋值给指针;
  • 访问指针变量中可用地址的值;
  • 通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值;
#include<iostream>
using namespace std;

int main(){
    int *p = nullptr;
    cout << sizeof(p) << endl; // 8

    char *p1 = nullptr;
    cout << sizeof(p1) << endl; // 8
    return 0;
}

指针的用法:
空指针:C 语言中定义了空指针为 NULL,实际是一个宏,它的值是 0,即 #define NULL 0。

C++ 中使用 nullptr 表示空指针,它是 C++ 11 中的关键字,是一种特殊类型的字面值,可以被转换成任意其他类型。

指针的运算:

  • 两个同类型指针可以比较大小;
  • 两个同类型指针可以相减;
  • 指针变量可以和整数类型变量或常量相加;
  • 指针变量可以减去一个整数类型变量或常量;
  • 指针变量可以自增,自减;
int a[10];
int *p1 = a + 1; // 指针常量相加
int *p2 = a + 4;
bool greater = p2 > p1; // 比较大小
int offset = p2 - a; // 相减
p2++; // 自增
p1--; // 自减

指向普通对象的指针:

#include <iostream>
using namespace std;

class A
{
};

int main()
{
    A *p = new A();
    return 0;
}

指向常量对象的指针:指针常量,const 修饰表示指针指向的内容不能更改。

#include <iostream>
using namespace std;

int main(void)
{
    const int c_var = 10;
    const int * p = &c_var;
    cout << *p << endl;
    return 0;
}

指向函数的指针:函数指针。

#include <iostream>
using namespace std;

int add(int a, int b){
    return a + b;
}

typedef int (*fun_p)(int, int);	
//函数指针,函数的返回值类型是int, 函数名为fun_p, 参数类型是(int int)
//如果将*fun_p前的()去掉
//那么int *fun_p(int, int), fun_p会优先与后面的()结合,fun_p是一个函数,返回值是int*

int main(void)
{
    fun_p fn = add;	//函数名等价于函数的地址	
    cout << fn(1, 6) << endl;//使用时传入函数名和参数
    return 0;
}

指向对象成员的指针,包括指向对象成员函数的指针和指向对象成员变量的指针。
特别注意:定义指向成员函数的指针时,要标明指针所属的类。

#include <iostream>
using namespace std;

class A
{
public:
    int var1, var2; 
	static int x;
	static int get() {
		return 100;
	}

    int add(){
        return var1 + var2;
    }

};


int main()
{
    A ex;
    ex.var1 = 3;
    ex.var2 = 4;
    int *p = &ex.var1; // 指向对象成员变量的指针
    cout << *p << endl;

    int (A::*fun_p)();
    int (*fun_q)();
    fun_p = &A::add; // 指向对象非静态成员函数的指针 fun_p
    fun_q = A::get;  // 指向对象静态成员函数的指针 fun_q
    cout << (ex.*fun_p)() << endl;
    cout << (*fun_q)() << endl;
    return 0;

}

而对于函数类型到函数指针类型的默认转换,只有当函数类型是左值的时候才行。所有对于非静态的成员函数,就不存在这种从函数类型到函数指针类型的默认转换,于是编译器也就不知道这个 p = A::add 该怎么确定。

由于非静态成员函数指针可以有多态行为,在编译期函数地址可能无法确定。

静态成员函数指针在编译期函数地址则可以确定。

this 指针:指向类的当前对象的指针常量。

#include <iostream>
#include <cstring>
using namespace std;

class A
{
public:
    void set_name(string tmp)
    {
        this->name = tmp;
    }
    
    void set_age(int tmp)
    {
        this->age = age;
    }
    
    void set_sex(int tmp)
    {
        this->sex = tmp;
    }
    
    void show()
    {
        cout << "Name: " << this->name << endl;
        cout << "Age: " << this->age << endl;
        cout << "Sex: " << this->sex << endl;
    }

private:
    string name;
    int age;
    int sex;
};

int main()
{
    A *p = new A();
    p->set_name("Alice");
    p->set_age(16);
    p->set_sex(1);
    p->show();

    return 0;

}

指针和引用的区别

指针:指针是一个变量,它保存另一个变量的内存地址。需要使用 * 运算符指针才能访问它指向的内存位置。
引用:引用变量是别名,即已存在变量的另一个名称。对于编译器来说,引用和指针一样,也是通过存储对象的地址来实现的。实际可以将引用视为具有自动间接寻址的常量指针,编译器自动为引用使用 * 运算符。
二者的区别

[引用](C:\Users\hp-pc\Desktop\C++\Essential C++\Essential C++ Notes.md)

  • 是否可变:
    指针所指向的内存空间在程序运行过程中可以改变,而引用所绑定的对象一旦初始化绑定就不能改变。
  • 是否占内存:
    指针本身在内存中占有内存空间,引用相当于变量的别名,在内存中不占内存空间(实际底层编译器可能用指针实现的引用),当我们使用 & 对引用取地址时,将会得到绑定对象的地址。
#include <iostream>
using namespace std;

int main() 
{ 
    int a = 10;
    int &b = a;
    cout << &a << endl;
    cout << &b << endl;
    return 0;
}
  • 是否可为空:
    指针可以定义时不用初始化直接悬空,但是引用初始化时必须绑定对象。
  • 是否能为多级
    指针可以有多级,但是引用只能一级。我们可以定义指针的指针,但不能定义引用的引用。

常量指针和指针常量的区别 ❤

常量指针:
常量指针本质上是个指针,只不过这个指针指向的对象是常量。
特点:const 的位置在指针声明运算符 * 的左侧。只要 const 位于 * 的左侧,无论它在类型名的左边或右边,都表示指向常量的指针。(可以这样理解:* 左侧表示指针指向的对象,该对象为常量,那么该指针为常量指针。)

const int * p;
int const * p;
  • 注意 1:指针指向的对象不能通过这个指针来修改,也就是说常量指针可以被赋值为变量的地址,之所以叫做常量指针,是限制了通过这个指针修改变量的值
    例如:

    #include <iostream>
    using namespace std;
    
    int main()
    {
        const int c_var = 8;
        const int *p = &c_var; 
        *p = 6;            // error: assignment of read-only location '* p'
        return 0;
    }
    
  • 注意 2:虽然常量指针指向的对象不能变化,可是因为常量指针本身是一个变量,因此,可以被重新赋值。
    例如:

    #include <iostream>
    using namespace std;
    
    int main()
    {
        const int c_var1 = 8;
        const int c_var2 = 8;
        const int *p = &c_var1; 
        p = &c_var2;
        return 0;
    }
    
    

指针常量:
指针常量的本质上是个常量,只不过这个常量的值是一个指针。
特点:const 位于指针声明操作符右侧,表明该对象本身是一个常量,*

左侧表示该指针指向的类型,即以 * 为分界线,其左侧表示指针指向的类型,右侧表示指针本身的性质。

const int var;
int * const c_p = &var; 
  • 注意 1:指针常量的值是指针,这个值因为是常量,所以指针本身不能改变。

    #include <iostream>
    using namespace std;
    
    int main()
    {
        int var, var1;
        int * const c_p = &var;
        c_p = &var1; // error: assignment of read-only variable 'c_p'
        return 0;
    }
    
  • 注意 2:指针的内容可以改变。

    #include <iostream>
    using namespace std;
    
    int main()
    {
        int var = 3;
        int * const c_p = &var;
        *c_p = 12; 
        return 0;
    }
    

指向常量的指针常量:
指向常量的指针常量,指针的指向不可修改,指针所指的内存区域中的值也不可修改。

#include <iostream>
using namespace std;

int main()
{
    int var, var1;
    const int * const c_p = &var;
    c_p = &var1; // error: assignment of read-only variable 'c_p'
    *c_p = 12; // error: assignment of read-only location '*c_p'
    return 0;
}

部分特例:
根据前三部分的结论,我们可以得到以下代码的表示内容:

int ** const p;  // p 是一指针常量,它是一个指向指针的指针常量;
int * const * p; // p 是一个指针,它是一个指向指针常量的指针;
int const ** p;  // p 是一个指针,它是一个指向常量的指针的指针;
int * const * const p; // p 是一指针常量,它是一个指向指针常量的指针常量;

函数指针的定义

函数指针:
**函数指针本质是一个指针变量,只不过这个指针指向一个函数。**函数指针即指向函数的指针。我们知道所有的函数最终的编译都生成代码段,每个函数的都只是代码段中一部分而已,在每个函数在代码段中都有其调用的起始地址与结束地址,因此我们可以用指针变量指向函数的在代码段中的起始地址。

#include <iostream>
using namespace std;

int fun1(int tmp1, int tmp2)
{
  return tmp1 * tmp2;
}

int fun2(int tmp1, int tmp2)
{
  return tmp1 / tmp2;
}

int main()
{
  int (*fun)(int x, int y); //声明一个函数指针,返回值为int型 函数名为fun 参数为(int, int)
  fun = fun1; // ok
  fun = &fun1; // ok 两种写法均可以
  cout << fun(15, 5) << endl; 
  fun = fun2;
  cout << fun(15, 5) << endl; 
  cout<<sizeof(fun1)<<endl; // error
  cout<<sizeof(&fun1)<<endl;
  return 0;
}
/*
运行结果:
75
3
*/

需要注意的是,对于 fun1 和 &fun1:

函数名 fun1 存放的是函数的首地址,它是一个函数类型 void,&fun1 表示一个指向函数对象 fun1 的地址,是一个指针类型。它的类型是 int (*)(int,int),因此 fun1 和 &fun1 的值是一样的;
&fun1 是一个表达式,函数此时作为一个对象,取对象的地址,该表达式的值是一个指针。
通过打印 sizeof 即可知道 fun1 与 &fun1 的区别;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值