C++学习笔记之指针(基础)
C/C++中的指针内容是早就盛名在外了,当然了,想要随心所欲地使用也是要做很多功课的,先简单了解下吧~
首先,我们需要了解一点内存 的概念:
每一个变量都有一个内存位置 ,而这个变量的内存位置会有地址 ,通过&
可以进行访问
cpp
int a = 10;
cout << &a << endl; // 访问变量地址
顺道再复习下数组
cpp
int a[10];
cout << &a << " " << &a[0] << endl; // 数组地址取的就是首个元素的地址,二者含义相同
好,明白这点以后,下面进入正题
1、指针是什么?
指针是一个变量 ,它的值为另一个变量的地址(内存位置的直接地址)
由于它作为一个变量,因此在使用之前也需要进行声明
cpp
类型 *变量名
- 类型:C++ 数据类型
- *:表明该变量是指针
- 变量名:合法的标识符
cpp
int *ip; // int类型指针
double *ptr; // double类型指针
注 :无论是int类型指针、double类型指针,还是其他各种类型的指针,作为指针变量的值,都是一样的,其实质都是内存地址 ,均为代表内存地址的长的十六进制数
各个类型指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同,也就是对应内存是存放了什么数据类型,这点是不同的
这就好比外卖员去送东西,外卖员去送外卖只关心顾客住在哪(内存地址),无论是屋子里住的是电影明星还是程序员(指向变量的类型),它只管按照地址把外卖送到就行了
2、使用指针
通常情况下,使用指针只需要三板斧 :
①定义一个指针变量
②把变量地址赋值给指针
③访问指针变量中可用地址的值
cpp
int value = 100;
int *ptr = &value; // 将变量地址给指针变量ptr
cout << *ptr << endl; // 访问指针地址对应的值,使用*
cout << ptr << endl; // 访问指针的值,为变量地址
*
的作用为解引用 ,用于获取该地址对应的内容,即指针指向变量的值
3、空指针
如果指针变量声明的时候,没有确切的地址可以赋值,通常会为指针赋值NULL
被赋值NULL
的指针称为空指针
NULL
在标准库中定义为0
cpp
int *ptr = NULL;
cout << ptr << endl; // 0
在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留 的,表明该指针不指向一个可访问的内存位置
cpp
int *ptr = NULL;
cout << ptr << endl;
if (ptr) // 通常使用类似的方式判断指针可用
{
// todo
}
如果所有未使用的指针都被赋予空值 ,同时避免使用空指针 ,就可以防止误用一个未初始化的指针 ,通常未初始化的变量存有一些垃圾值,导致程序难以调试
4、指针的算术运算
C++指针实际上是地址,因此可以对指针进行一些算术运算 ,包括++
、--
、+
、-
指针的算术运算根据指针的类型和大小 决定移动的距离,比如如果是int
类型的指针,int
所占字节为4字节,即32位,那么指针每移动一个单位,便是4字节
-
加法运算
指针当前指向的地址的基础上加n个单位,每个单位由对应指针的类型决定
在使用指针操作数组时,常常使用这些指针运算符,数组的类型统一,因而下一个元素正好可以方便地使用指针的加减来寻址cppint arr[] = {1, 2, 3, 5, 4}; int *ptr = arr; // 指向数组首地址 cout << *ptr << endl; ptr ++; // 数组下一个元素 cout << *ptr << endl;
但是需要注意,当使用指针操作时,要确保指针指向有效的内存区域 ,否则可能会导致未定义行为或程序崩溃
在操作数组时,尤其要小心避免指针超出数组的范围 -
减法运算
与加法相反,在当前指针指向的地址的基础上减去n个单位cppint arr[] = {1, 2, 3, 5, 4}; int *ptr = &arr[1]; // 指向数组第二个元素的地址 cout << *ptr << endl; ptr --; // 向前移一位元素 cout << *ptr << endl;
-
指针与指针间的减法
两个指针间的元素个数cppint arr[] = {1, 2, 3, 5, 4}; int *ptr1 = &arr[1]; int *ptr2 = &arr[3]; cout << ptr2 - ptr1 << endl; // 相距2个元素
-
指针与整数间的比较
可以将指针与整数进行比较运算,常用于判断指针是否指向某个有效的内存位置cppint arr[] = {1, 2, 3, 5, 4}; int *ptr1 = &arr[1]; int *ptr2 = &arr[3]; cout << (ptr1 == ptr2) << endl; // 假,返回0 cout << (ptr1 < ptr2) << endl; // 真,返回1
需要注意,在进行关系比较时,指针需要属于同一数组 ,否则关系比较结果是未定义的
并且,在进行关系比较前,需要确保指针非空 ,否则也会产生未定义的行为
5、指针 vs 数组
在C++中,指针和数组是密切相关的,透过表象看本质,终究还是对内存地址的操作
数组是内存中连续的单元组合而成,因此其中每个元素是同样的类型,这就可以借助于指针在数组上进行运算,因为每一步正好就是一个类型长度,从数组首地址开始根据计算可以访问其中的各个元素
cpp
int arr[] = {1, 2, 3, 5, 4};
int *ptr = arr;
for (int i = 0; i < 5; i++)
{
cout << *ptr << endl;
ptr ++;
}
然而,两者之间是无法完全画等号的,如果把数组名看做指针,使用*
更换内容,是可以的,但是使用++
一类的运算来修改,是不行的,因为修改值不会涉及地址的变化,而使用++
等运算符,数组的首地址就变了呀,那还不乱套了
看到报错提示,数组名是不可作为左值的,它的地址不可被改变
数组名属于指向数组开头的常量,因而不能重新对其修改,但是其中内容可以更改,也可以采用指针的方式进行访问
cpp
*(arr + 2) // 相当于arr[2] 结果为3
6、指针数组
前面学习了数组,现在又了解了一些指针的内容,此时将二者结合一下,便有了指针数组的概念
其实这个比较好理解的,也就是数组的每个元素都是一个指针,指向某个元素的地址
cpp
int arr[] = {1, 3, 9};
int *ptr[3];
for (int i = 0; i < 3; i++)
{
ptr[i] = &arr[i]; // 取整数的地址
}
for (int i = 0; i < 3; i++)
{
cout << *ptr[i] << endl;
}
在下面这个例子中,也是一个指针数组,看右边知道,每个元素是字符串,下意识反应字符串其实就是字符数组,数组在某种程度上相当于指针,所以意味着,我们可以把每个元素看做字符指针,那么就可以将字符串作为字符指针数组的元素了,这种转换意识还是需要的
cpp
const char *ptr[3] = {
"Bob",
"Bill",
"Mike"
};
for (int i = 0; i < 3; i++)
{
cout << "name=" << ptr[i] << endl;
}
7、指向指针的指针
指向指针的指针,听起来好绕的样子,但是带入生活的案例可能好理解一些,指针对应着地址,比方说我想要认识甲,但是我不知道他的地址,但我知道乙的地址,乙知道甲的地址,那么我通过乙去找甲,这便是指针的指针,像极了人际关系网
我和乙都是指针,我们掌握的都是地址,而真正需要的东西在甲手里
声明指针的指针需要使用**
cpp
int **ptr;
看看简单的使用:
cpp
int **pptr;
int *ptr;
int value = 100;
ptr = &value; // 乙知道甲的地址
pptr = &ptr; // 我知道乙的地址
cout << value << endl; // 甲手里的100
cout << *ptr << endl; // 乙找到甲得到100
cout << **pptr << endl; // 我找到乙,通过乙找到甲得到100
8、传递指针给函数
C++ 允许传递指针给函数,只需要简单地声明函数参数为指针类型即可
cpp
int getSum(int *arr, int size) {
int sum = 0;
for (int i = 0; i < size; i++)
{
sum += arr[i];
}
return sum;
}
int main()
{
int arr[] = {1, 2, 3, 4, 5};
cout << getSum(arr, 5) << endl;
}
9、从函数返回指针
C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static
变量
cpp
int *getArray( )
{
static int arr[3] = {1, 2, 3}; // 局部变量必须是static
return arr;
}
int main ()
{
int *p;
p = getArray();
for ( int i = 0; i < 3; i++ )
{
cout << *(p + i) << endl;
}
return 0;
}
使用static
是为了防止局部变量在函数结束的时候失效,随函数一同出栈,使用static
会将该变量放到全局区,即使方法栈结束变量依然有效
先看这么多,估计够消化一阵子了,其余的在实践中慢慢学吧~~