指针及其大小、用法
指针的定义:
指针是一种变量类型,其值为另一个变量的地址,即内存位置的直接地址。就像其他变量或常量一样,必须在使用指针存储其他变量地址之前,对其进行声明。在 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 的区别;