好久没有更新推文了,最近换了工作,时间相对多了一点,有一点时间把过去的一些笔记内容给整理一下。能坚持学习和整理是一件很难的事情,当下大多数人的生活都相当碎片化,很多事做着做着就中断了,希望我能把我学习C++和OpenFOAM的一些内容写完。
指针在OpenFOAM里面是一个很常见的内容,例如使用auto关键字,智能指针。指针本质上就是C/C++中提供一种直接访问内存数据的一种方式,效率高,但是用法复杂一些。很多时候,需要指针做的事情,使用引用也可以,尽量避免指针的使用,因为指针使用不当,容易造成内存泄漏,造成程序错误和崩溃。
指针的声明和使用
指针的声明很简单,一般是类型名*。和引用方式不同,指针是可以使用空指针进行初始化的,而不是需要指向一个已经存在的变量的内存地址。
假设现在有一个指针类型p,并且p并不是空指针。*p就是解引用操作,可以访问p所对应的内存地址中的变量内容,并且在没有const关键字限定的情况下,可以对变量值进行修改。
#include<iostream>
using namespace std;
int main()
{int a = 10;int* p11 = NULL;//C++11之前的空指针int* p2q(nullptr); //现代C++空指针声明方式,都可以使用p11 = &a; //这里&号取地址符,获取变量的内存地址p2q = &a; //可以多个指针指向一个内存地址,指向哪个地址就可以通过指针访问内存数据cout<<"p11的地址为:"<<p11<<"(解引用)其值为:"<<*p11<<endl;// *运算符搭配类型名时,可以声明指针// 搭配指针时,为间接寻址运算符,可以访问内存的值,这一操作为解引用cout<<"p2q的地址为:"<<p11<<"(解引用)其值为:"<<*p2q<<endl;//如果需要限制指针的指向,可以搭配const关键词使用//同样可以对指针访问和内存变量的值*p11 = 100;cout<<"变量a的值为:"<<a<<endl;//搭配输入输出操作cin>>*p11; //输入值200cout<<"变量a的值为:"<<a<<endl;return 0;
}
const限定
使用const关键字可以对指针的指向,内存数据是否可修改做出限定。基本上有四种情况:
-
非常量指针指向非常量数据
这种情况就是没有const关键字修饰,如上面的代码所示。指针可以被修改指向别的变量,并且可以通过这个指针修改内存变量的值。
-
非常量指针指向常量数据
通过const + 类型名* + 指针名进行定义,指向某一个内存变量。但是不能够通过该指针修改内存变量的值。不过可以将该指针指向其他内存变量。一般在函数中,或者类中引用其他类/对象时,出于保护被使用对象的目的,不改变对象值。
-
指向非常量数据的常量指针
通过类型名* + const + 指针名进行定义,该指针只能指向一个内存变量,且不能被修改指向其他内存变量。不过可以通过该指针修改内存变量值。这种指针就是对指针的指向范围进行了限制,将指针绑定在某一个内存变量上,成为该变量的“专属指针”。当然一个内存变量可以有多个专属指针。
-
常量指针指向常量数据
该指针就是前面两种指针的结合体,通过const + 类型名* + const进行限定,该指针既不能被修改指向其他内存变量也不能够修改内存变量的值。
#include<iostream>
#include<array>
using namespace std;
int main()
{const int value[] = {11,12,13,14,15};std::array arrayint(std::to_array(value));for(int i = 0;i<arrayint.size();i++){cout<<arrayint[i]<<endl;}//或者使用迭代器遍历cout<<"使用迭代器遍历array"<<endl;for(std::array<int,arrayint.size()>::iterator it = arrayint.begin();it != arrayint.end();it++){cout<<*it<<endl;}//auto关键词,让C++自己去推断变量的类型和大小//指向常量数据的非常量指针int a = 100;const int* p{&a};//*p = 200; //error, 不可以通过该类型指针修改变量数据,表达式是可被修改的左值cout<<"指针指向的内容是:"<<*p<<endl;int b = 200;p = &b; //不能修改变量值,但是可以更换指向别的变量cout<<"指针指向的内容是:"<<*p<<endl;//指向非常量数据的常量指针int* const p2 = &a; //该类型指针可以修改变量值,但是不能再指向其他变量cout<<"p2指针指向的内容是:"<<*p2<<endl;*p2 = 1000;cout<<"p2指针指向的内容是:"<<*p2<<endl;//p2 = &b; //不可以指向其他变量//指向常量数据的常量指针//只能读取数据,既不能修改变量值,也不能指向其他内存地址const int* const p3 = &a;cout<<"p3指针指向的内容是:"<<*p3<<endl;return 0;
}
程序的输出为:
11
12
13
14
15
使用迭代器遍历array
11
12
13
14
15
指针指向的内容是:100
指针指向的内容是:200
p2指针指向的内容是:100
p2指针指向的内容是:1000
p3指针指向的内容是:1000
指针的运算
在学C语言时候,就提到数组其实也是一种指针,之前的文章也说过,函数也有函数指针。数组的名字就是一种指针名。如果我们访问数组中的某一个数据,通常是数组名[索引]。在连续内存中,指针的加减并不是简单的对指针的地址加减相应的值,而是移动指针指向的内存地址。移动的距离为sizeof(内存变量类型)。
当对指针进行加法运算时,并不是简单地按照常规数值相加的方式来处理。实际上,指针加法的结果取决于指针所指向的数据类型的大小(字节数)。例如,如果指针指向 int
类型(通常 int
在常见系统中占 4 个字节),那么指针加 1,实际上是让指针在内存中的地址向后移动了 sizeof(int)
(也就是 4 个字节)的距离,使其指向下一个 int
类型元素所在的内存位置。
和指针加法类似,指针减法也是基于所指数据类型的大小来操作的。指针减 1,就是让指针在内存中的地址向前移动对应数据类型大小的距离,使其指向前一个同类型元素所在的内存位置。另外,两个同类型的指针相减,可以得到它们之间相隔元素的个数(以对应的数据类型为单位)。例如,有两个指向 int
数组元素的指针 p1
和 p2
,p1 - p2
的结果就是它们之间间隔了几个 int
类型的元素个数。
使用数组名作为函数参数传递的方法,现在已经不提倡了,因为安全性存在问题。给出数组名,给出数组第一个元素的内存地址,但是没有给出数组的长度。在实际使用时,会容易出现访问不存在的内存变量导致程序崩溃。
#include <iostream>
using namespace std;int main() {// 示例 1:指针加法遍历数组int arr[] = {1, 2, 3, 4, 5};int* ptr = arr; // 让指针指向数组的首元素for (int i = 0; i < 5; ++i) {cout << *ptr << " "; // 输出当前指针指向的元素值ptr = ptr + 1; // 指针向后移动一个元素位置(地址增加 sizeof(int) 字节)}cout << endl;// 示例 2:指针减法计算间隔元素个数int arr2[] = {10, 20, 30, 40, 50};int* p1 = &arr2[2]; // 指向数组中第三个元素(索引为 2)int* p2 = &arr2[0]; // 指向数组的首元素int diff = p1 - p2; // 计算两个指针之间间隔的元素个数cout << "两个指针之间间隔的元素个数为: " << diff << endl;return 0;
}
指针和引用的简单说明
指针指向内存变量的地址,可以直接修改内存变量的值。而引用,其特性一句话就是变量的“别名”。在函数中的体现尤为明显,下面为交换两个数的程序:
#include<iostream>
using namespace std;void swap(int a,int b)
{int c = a;a = b;b = c;
}// void swap(int& a,int& b) //使用引用的方式交换两个变量
// {
// int c = a;
// a = b;
// b = c;
// }// void swap(int* a, int*b ) //以传值的形式传地址给形参a和b,实际上并不能起到交换数据的作用
// {
// int *c = a;
// a = b;
// }// void swap(int* a, int*b ) //通过内存变量的形式实现数据交换
// {
// int c = *a;
// *a = *b;
// *b = c;
// }
int main()
{int m = 5,n=10;cout <<"交换数据前: "<<"m = "<<m<<" n = "<<n<<endl;swap(m,n);cout <<"交换数据后: "<<"m = "<<m<<" n = "<<n<<endl;return 0;
}
函数的传入参数为形参,本质是传值。在这段程序中,a和b只是拷贝了m和n的值。但是因为a和b只是临时变量,有作用域限制,只在swap函数中起作用,执行完函数并没有真正交换a和b的值。
交换数据前: m = 5 n = 10
交换数据后: m = 5 n = 10
而下面的函数,则是引用的用法,引用相当于函数的别名。程序中,a和b相当于是m和n的别名了。其实已经和m、n在内存中的绑定。a和b的修改是能够修改m和n的值。
void swap(int& a,int& b) //使用引用的方式交换两个变量
{int c = a;a = b;b = c;
}
交换数据前: m = 5 n = 10
交换数据后: m = 10 n = 5
下面的函数中,a和b只是拷贝了m和n的地址,如果不通过指针,访问内存中的数据并修改,其实也仅仅是拷贝内存变量的地址而已。a和b仍然是作用域局限在swap函数中的变量,当函数执行完毕,m和n的值也并不会交换。
void swap(int* a, int*b ) //以传值的形式传地址给形参a和b,实际上并不能起到交换数据的作用
{int *c = a;a = b;
}
交换数据前: m = 5 n = 10
交换数据后: m = 5 n = 10
如果我们使用间接寻址运算符,进行解引用操作,访问非常量数据并且进行修改,就可以实现数据的交换。
void swap(int* a, int*b ) //通过内存变量的形式实现数据交换
{int c = *a;*a = *b;*b = c;
}
交换数据前: m = 5 n = 10
交换数据后: m = 10 n = 5
在使用指针和引用时,还有左值和右值的概念和区分。这一块留待下次写智能指针的内容。
简单地说,变量名,是直接操作内存变量的方法,避免了通过内存地址去查找变量并且做读取和修改操作。引用是变量的别名。引用必须得有实际变量存在做初始化。指针则指向一个内存地址,通过指针寻址和解引用操作访问和修改变量值。
下面代码简单说明了一下指针和引用的关系:
#include<iostream>
using namespace std;int main()
{int m = 5,n=10;int& k = m;cout<<"m的值: "<<m<<" k的值为:"<<k<<endl; //k是m的引用int* p = &m; //需要注意&符号的用法,可以用来声明引用,也可以做取地址符号cout<<"m的值: "<<m<<" k的值为:"<<k<<" p指向的值为: "<<*p<<endl;*p = 100;cout<<"修改后m的值: "<<m<<" k的值为:"<<k<<" p指向的值为: "<<*p<<endl;int* q = &k; //这里对引用取地址,实际上仍然对m取地址cout<<"p指针地址为: "<<p<<" q指针地址为: "<<q<<endl;//引用不占据内存空间,而指针占用,推荐使用引用,现代C++对指针开始逐步淡化//有指向指针的引用,相当与指针的“别名”,从左往右看操作符int* &r = p;cout<<"p指针地址为: "<<p<<" q指针地址为: "<<q<<" r指针的地址为: "<<r<<endl;cout<<*p<<" "<<*q<<" "<<*r<<endl;//使用指针的引用改变值*r = 200;cout<<"修改后: "<<*p<<" "<<*q<<" "<<*r<<endl;//不存在指向引用的指针,因为引用并无实际内存地址,做变量别名return 0;
}
m的值: 5 k的值为:5
m的值: 5 k的值为:5 p指向的值为: 5
修改后m的值: 100 k的值为:100 p指向的值为: 100
p指针地址为: 0x7ffcc0673ee0 q指针地址为: 0x7ffcc0673ee0
p指针地址为: 0x7ffcc0673ee0 q指针地址为: 0x7ffcc0673ee0 r指针的地址为: 0x7ffcc0673ee0
100 100 100
修改后: 200 200 200