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

news/2024/11/24 5:35:19/

指针及其大小、用法

指针的定义:

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

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

  • 定义一个指针变量;
  • 把变量地址赋值给指针;
  • 访问指针变量中可用地址的值;
  • 通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值;
#include<iostream>
using namespace std;int main(){int *p = nullptr;cout << sizeof(p) << endl; // 8char *p1 = nullptr;cout << sizeof(p1) << endl; // 8return 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_pfun_q = A::get;  // 指向对象静态成员函数的指针 fun_qcout << (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; // okfun = &fun1; // ok 两种写法均可以cout << fun(15, 5) << endl; fun = fun2;cout << fun(15, 5) << endl; cout<<sizeof(fun1)<<endl; // errorcout<<sizeof(&fun1)<<endl;return 0;
}
/*
运行结果:
75
3
*/

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

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


http://www.ppmy.cn/news/36706.html

相关文章

Java设计模式-观察者模式

1 概述 定义&#xff1a; 又被称为发布-订阅&#xff08;Publish/Subscribe&#xff09;模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时&#xff0c;会通知所有的观察者对象&#xff0c;使他们…

最小均方混音方法

一、简介&#xff1a; 一种利用数字信号处理技术实现的混音方法&#xff0c;它可以将多路信号混合成一个信号&#xff0c;并最小化混合信号与原始信号的误差平方和。该方法可以用于音频信号、图像信号等多种信号处理场景。 二、原理&#xff1a; 利用线性代数中的矩阵分解和最…

提交sql后执行过程

摘要: 在如今大数据时代,为了提升研发效率各个公司基本都是用sql进行数据开发,那么一条sql在提交后是怎样运行在spark中呢?回答以上问题需要先了解sql执行的主要流程,以及这个流程中每一步骤具体都是做什么的。 主要流程 语法树解析-逻辑分析-语法优化-计划执行 详细拆解…

0504反常积分-定积分

文章目录1 无穷限的反常积分1.1 定义1.2 计算公式1.3 例题2 无界函数的反常积分2.1 定义2.2 计算公式2.3 例题结语1 无穷限的反常积分 1.1 定义 设函数f(x)在区间[a,∞)上连续&#xff0c;人去t>a,做定积分∫atf(x)dxf(x)在区间[a,\infty)上连续&#xff0c;人去t\gt a,做…

优先级队列和TopK问题

一、优先级队列PriorityQueue 优先级队列是基于堆实现的&#xff1a; 入队&#xff1a;堆的add()方法 出队&#xff1a;按照优先级出队&#xff0c;优先级最高的先出&#xff08;堆顶元素&#xff09; 调用堆的extractMax方法&#xff0c;按降序排列输出 查看队顶元素&#xf…

vue3与vue2的区别

文章目录1.性能提升&#xff1a;2.Composition API&#xff1a;3.更好的 TypeScript 支持&#xff1a;4.Teleport 组件&#xff1a;5.其他改进&#xff1a;1.性能提升&#xff1a; Vue3 重写了响应式系统&#xff0c;使得它的性能比 Vue 2 更高。Vue 3 中使用了 Proxy 来代替 …

嵌入式硬件设计与实践(从硬件到产品)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 很多同学会画电路板&#xff0c;也会写固件代码&#xff0c;但是他们做的这项工作很难称之为产品。这中间的原因是多方面的&#xff0c;第一&#…

MySQL-配置优化

目录 &#x1f341;连接请求的变量 &#x1f342;max_connections &#x1f342;back_log &#x1f342;wait_timeout和interactive_timeout &#x1f341;缓冲区变量 &#x1f342;key_buffer_size &#x1f342;query_cache_size &#x1f342;max_connect_errors &#x1f3…