C++ 指针常见用法小结
本文参考资料 C++ Primer, 5e; Coursera北大数据结构与算法课程。
1. 概论
指针在C\C++语言中是很重要的内容,并且和指针有关的内容一向令人头大。针对初学者,我总结了一些关于指针和数组的用法(尤其是指针和二维数组)。初学者大部分关于指针和数组的问题应该可以再本文找到答案,高级用法我也没有接触到,就这样吧。
2.指针基础
指针是指向另外一种类型的复合类型。
指针本身就是一个对象,允许对指针进行赋值和拷贝;指针无需在定义时赋初值。
指针定义
"&"是取地址操作符。
int num=1;
int *p=# //(&是取地址操作符)
利用指针访问对象
使用解引用操作符“*”。
cout<<*p<<endl;
输出结果为1。
指针的状态
- 指向一个对象
 - 指向紧邻对象所占空间的下一个位置
 - 空指针 int *p=nullptr;
 - 无效指针
 
指针作为条件判断参数
例如:
if(p){}
只要指针p不是0,那么条件就为真。
另外值得注意的是,对于两个类型相同的指针,可以用“==”或者“!=”来比较。若两个指针存放的地址相同,则它们相等,否则不等。
3. 指针进阶
指向指针的指针
  由于指针是对象,所以指针也有自己的地址。因此,C++语言允许把一个指针指向另一个指针。
  例子:
int i=9;
int *p1=&i;
int **p2=&p1;
cout<<i<<endl<<*p1<<endl<<**p2<<endl;
结果是打印3个9。
指针与const限定符
这里有2个初学者容易混淆的概念,即指向常量的指针和常量指针。根据其英文名字可能比较容易记住:
指向常量的指针(pointer to const)是说这个指针是一个普通的指针,它指向了一个常量,如果你愿意,它也可以指向其他对象,并且可以令一个指向常量的指针指向另一个非常量;
const double pi=3.1415;
double *p1=π//error for p1 is a general pointer
const double *p2=π//correct; and p2 can poinnt to other objects
*p2=6.28;//error for pi is a const variable and p2 is const
常量指针(const pointer)是说这个指针本身就是一个常量对象,所以它不能指向其他对象,但是不意味着它不能改变所指向对象的值。
int num=9;
int *const p1=#//correct, but remember that p1 cannot point to other objects
*p1=18;//correct. You can use the const pointer to change the value of a unconst variable
const double e=2.71;
const double *const p2=&e;//p2 is a const pointer points to a const object
4. 一维数组的定义与初始化
定义
int arr[10];//含有10个整型的数组
int *arr2[3];//含有3个整型指针的数组
一般情况下,数组的元素被默认初始化。
显示初始化
int arr[]={1,2,3};
int arr2[4]={1,2,3,4};
可以用字符串字面值初始化字符数组,但是需要记得字符串字面值结尾有一个空字符。
char arr[5]={'h','e','l','l','o'};//correct
char arr[5]="hello";//error, initilizer-string for the chars array is too long
访问数组元素
使用下标访问数组元素,注意数组的下标从0开始。
C++ 11标准增加了 range for语句可以遍历数组元素:
for(auto i : arr)//auto用来自动确定类型
{
    cout<<i<<endl;
}
使用range for的好处在于不用担心数组越界。
5. 指针和数组
可以用一个指针指向数组元素:
int arr[]={1,2,3,4,5,6,7,8,9,0};
int *p=&arr[0];//此时p是一个指向数组首元素的指针
数组有一个特性,很多用到数组的地方,编译器会自动把数组名替换为一个指向数组首元素的指针:
int arr[]={1,2,3,4,5,6,7,8,9,0};
int *p=&arr[0];//此时p是一个指向数组首元素的指针
cout<<p<<endl;//result 0x69fee4
cout<<arr<<endl;//result 0x69fee4
注意:由于数组名arr是一个常量,因此*arr++是没有意义的,并且编译器会报错,因为arr++试图修改arr的值。但是(arr+2)是有意义的,因为这并没有试图修改arr的值。同理,如果令p=&arr[0],那么我们也是可以使用p++的,因为p不是常量。
标准库函数begin & end
尽管可以得到尾后指针,但这种做法极易出错。C++11标准引入了两个名为begin和end的函数:
int a[]={1,2,3,4,5,6};
int *beg=begin(a);//pointer to the first element
int *last=end(a);//pointer to the position next to the last element
//output the elements of the array
while(beg!=end)
{
cout<<*beg<<endl;
++beg;
}
6. 指针运算
给一个指针加上(减去)某个整数N,结果依然是指针。新指针与原来的指针相比前进或者后退了N个位置。
两个指针相减的结果是它们之间的距离。
为了更好的理解这个问题,举如下例子:
#include <iostream>
using namespace std;
int main()
{
int a[3][3]={{6,1,7},
{2,5,4},
{8,3,9}
};
<span class="hljs-built_in">cout</span><<a<<<span class="hljs-built_in">endl</span>;<span class="hljs-comment">//1</span>
<span class="hljs-built_in">cout</span><<a+<span class="hljs-number">1</span><<<span class="hljs-built_in">endl</span>;<span class="hljs-comment">//2</span>
<span class="hljs-built_in">cout</span><<&a+<span class="hljs-number">1</span><<<span class="hljs-built_in">endl</span>;<span class="hljs-comment">//3</span>
<span class="hljs-built_in">cout</span><<*a<<<span class="hljs-built_in">endl</span>;<span class="hljs-comment">//4</span>
<span class="hljs-built_in">cout</span><<*a+<span class="hljs-number">1</span><<<span class="hljs-built_in">endl</span>;<span class="hljs-comment">//5</span>
<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
  程序结果(你的结果可能会有所不同)
  0x69fec0
  0x69fecc
  0x69fee4
  0x69fec0
  0x69fec4
第一个打印结果为0x69fec0,给a+1后,结果为0x69fecc,变大了12。为什么会变大12呢?要知道每个整数都是4个字节,为什么不是变大4呢?答案是:由于a是一个二维数组,所以a指向的第一个元素是一个含有3个整数的数组,因而加1后是指向下一个子数组,所以地址的值会变大12.
同理,&a是指向一个二维数组,因此加1后地址值会变大0x 24=36。
而a指向第一个子数组的第一个元素——这是一个整数,因此加1后地址值变大了4。同时你可以发现a和a的地址是一样的,这是因为第一个子数组和第一个整型元素的起始地址是一样的。
7. 多维数组和指针
严格来说,C++中是没有多维数组的,通常所说的多维数组其实是数组的数组。
在本文第6部分的例子中展示了二维数组的初始化方法,更高维的数组初始化方法是类似的。这里再次详细说明一下数组名和指针的关系。以本文第6部分的例子为例:
a是指向二维数组第一个元素即第一个子数组的指针,等价于&a[0];
a[0]是指向a[0][0]的指针,等价于*a;
a[0][0]指向第一个整型元素‘6’, 等价于**a;
&a指向整个二维数组;
总之,*会将指针降一级,&会把指针升一级。
下标访问
多维数组同样可以下标访问,例如a[0][0]的值是6.
8. 指针形参
当使用指针作为函数参数的时候,执行的是指针拷贝的操作,拷贝的是指针的值。拷贝之后两个指针是不同的指针,但是它们所指向的对象是一样的,因此可以通过操作指针来改变指针所指向对象的值。
void change(int *p)
{
    *p=32;
}
限制指针的功能
很多情况下我们使用指针是为了避免拷贝对象,但是并不希望更改对象的值。这种情况下,使用const限定符限制指针的功能是一个不错的选择。
void test(const int *p)
{
    ...
    ...
}
9. 数组形参
由于数组不允许拷贝,因此我们无法以传值的方式传递一个数组;因为数组名相当于数组第一个元素的指针,因此可以通过传递指针的形式来在函数中操作数组。
以下3个声明是等价的:
void print(const int *);
void print(const int[]);
void print(const int[5]);
由于数组是以指针形式传递给函数的,因此一开始的时候函数并不知道数组的维度。因此有时候有必要显示传递一个维度参数。
当函数不需要对数组元素进行写操作的时候,数组形参最好用const限定符限制指针功能,详见本文第8部分示例。
传递多维数组
所谓多维数组其实是数组的数组。和一维数组一样,我们实际传递的是数组的指针。下面2个声明是等价的:
void print(int (*p)[3],int rowsize){...}
void print(int (p[][3],int rowsize){...}
这样的例子可能没有什么直观的感受,下面我们用一个详细的例子来说明。
#include <iostream>
using namespace std;
void print1(int (p)[3])//注意p两边的括号不可缺少。
{
cout<<p[1][1]<<endl;
}
void print2(int p[][3])
{
cout<<p[0][0]<<endl;
}
int main()
{
int a[2][3]={{1,2},{3,4}};
print1(a);
print2(a);
return 0;
}
注意,print1(int (p)[3])函数里面,形参p两边的括号必不可少。
p[3]表示3个指针构成的数组
(p)[3]表示指向含有3个整数数组的指针。
  结果
  4
  1
你可能对这种方法并不是很满意,为什么呢?因为使用这种方式必须显示指定第二维的维度,而有些时候这个维度是无法获得的。比如有以下题目:
摘抄自:北大poj
描述
在一个m×n的山地上,已知每个地块的平均高程,请求出所有山顶所在的地块(所谓山顶,就是其地块平均高程不比其上下左右相邻的四个地块每个地块的平均高程小的地方)。输入
第一行是两个整数,表示山地的长m(5≤m≤20)和宽n(5≤n≤20)。
其后m行为一个m×n的整数矩阵,表示每个地块的平均高程。每行的整数间用一个空格分隔。输出
输出所有上顶所在地块的位置。每行一个。按先m值从小到大,再n值从小到大的顺序输出。
样例输入
10 5
0 76 81 34 66
1 13 58 4 40
5 24 17 6 65
13 13 76 3 20
8 36 12 60 37
42 53 87 10 65
42 25 47 41 33
71 69 94 24 12
92 11 71 3 82
91 90 20 95 44
样例输出
0 2
0 4
2 1
2 4
3 0
3 2
4 3
5 2
5 4
7 2
8 0
8 4
9 3
如果题目要求你必须写一个函数来处理,是不是感觉之前讲解的参数传递方法就不实用了呢?因为你也不知道一开始第二个维度是多少啊!
其实万变不离其宗,我们只要想办法传入一个地址,就可以通过这个地址访问到这个数组的所有元素。这里最重要的就是要搞清楚本文第6部分和第7部分讲解的关于指针运算的问题,这对于初学者可能会感觉有点乱,但是只要慢慢去想,你就会发现这一切都是那么的自然。
为了解决这个问题,首先看看如何访问整个二维数组。
原则上只要传入了第一个元素的指针,我们就可以通过对这个指针进行运算从而遍历整个数组。
void print (int *a,int m,int n)
{
    for(int i=0;i!=m;++i)
    {
        for(int j=0;j!=n;++j)
            cout<<*(a+i*n+j);
        cout<<endl;
    }
}
假设有一个55的二维数组a。这个函数,我们可以传入print(a,5,5)。还记得吗?在第7部分我们说过a就是指向a[0][0]的指针。这样就可以打印整个数组了。我记得我刚学习这里的时候总是纠结于,我是不是在定义函数的时候形参是不是应该是print(intp, int m, int n)呢?其实是没有必要的,因为二维数组名并不是指向指针的指针*,你需要的只是一个入口而已。
下面给出傻瓜式程序:
#include <iostream>
using namespace std;
static int m,n;
void hill(int **a)
{
    for(int i=0;i!=m;++i)
    {
        for(int j=0;j!=n;++j)
        {
            int num=*((int*)a+i*n+j);
            if(i==0)
            {
               if(j==0)
               {
                   if(num>=*((int*)a+i*n+j+1) && num>=*((int*)a+(i+1)*n+j))
                   {
                       cout<<i<<" "<<j<<endl;
                   }
               }
               else if(j==n-1)
               {
                   if(num>=*((int*)a+i*n+j-1) && num>=*((int*)a+(i+1)*n+j))
                   {
                       cout<<i<<" "<<j<<endl;
                   }
               }
               else
               {
                    if(num>=*((int*)a+i*n+j-1) && num>=*((int*)a+i*n+j+1) && num>=*((int*)a+(i+1)*n+j))
                    {
                        cout<<i<<" "<<j<<endl;
                    }
               }
            }
            else if(i==m-1)
            {
                if(j==0)
               {
                    if(num>=*((int*)a+(i-1)*n+j) && num>=*((int*)a+i*n+j+1))
                    {
                        cout<<i<<" "<<j<<endl;
                    }
               }
               else if(j==n-1)
               {
                   if(num>=*((int*)a+(i-1)*n+j) && num>=*((int*)a+i*n+j-1))
                   {
                       cout<<i<<" "<<j<<endl;
                   }
               }
               else
               {
                    if(num>=*((int*)a+(i-1)*n+j) && num>=*((int*)a+i*n+j-1) && num>=*((int*)a+i*n+j+1))
                    {
                        cout<<i<<" "<<j<<endl;
                    }
               }
            }
            else
            {
               if(j==0)
               {
                   if(num>=*((int*)a+(i-1)*n+j) && num>=*((int*)a+(i+1)*n+j) && num>=*((int*)a+i*n+j+1))
                   {
                       cout<<i<<" "<<j<<endl;
                   }
               }
               else if(j==n-1)
               {
                    if(num>=*((int*)a+i*n+j-1) && num>=*((int*)a+(i-1)*n+j) && num>=*((int*)a+(i+1)*n+j))
                        cout<<i<<" "<<j<<endl;
               }
               else
               {
                    if(num>=*((int*)a+i*n+j-1) && num>=*((int*)a+i*n+j+1) && num>=*((int*)a+(i-1)*n+j) && num>=*((int*)a+(i+1)*n+j))
                        cout<<i<<" "<<j<<endl;
               }
            }
        }
    }
}
int main()
{
<span class="hljs-built_in">cin</span>>>m>>n;
<span class="hljs-keyword">int</span> a[m][n];
<span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i=<span class="hljs-number">0</span>;i!=m;++i)
{
    <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> j=<span class="hljs-number">0</span>;j!=n;++j)
    {
        <span class="hljs-built_in">cin</span>>>a[i][j];
    }
}
<span class="hljs-comment">//int *p=*a;</span>
hill((<span class="hljs-keyword">int</span>**)a);
<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
10. 返回指针和数组
返回指针
#include<iostream>
using namespace std;
int a[]={11,21,31,41};
int *f()
{
    return a;
}
int main()
{
cout<<*f()<<endl;//result is 11
}
上面的例子展示了如何返回一个指针。或许把函数定义写成如下形式更好理解。
int* f(){... ...}
这种形式展示了函数 f 的返回类型是指针而不是让人误以为函数名是 *f。
永远不要试图返回局部对象的指针。因为局部变量(对象)的生命周期在函数调用结束后会消失,此时你返回的地址的内容可能已经发生了变化。
如果确实需要返回局部变量的指针,你需要把这个变量声明为static
返回数组
由于指针不能拷贝,因此函数不能返回数组,但是可以返回数组的指针或者引用。其实上面的例子就是一个返回数组的例子,故不再叙述。
11. 结语
本文主要讲述了指针和数组的用法。我觉得这对于初学者还是一个比较全面的教程,希望大家喜欢。
原文地址:https://blog.csdn.net/valada/article/details/79909749
C++ 指针常见用法小结的更多相关文章
- flex布局常见用法小结
		
1,display:flex 这个在父容器中声明: 2,flex-direction:row / column 默认为横向,也在父容器中设置,定义flex布局的主轴方向:一条轴为主轴,那么另一条轴自然 ...
 - go中指针类型的用法小结
		
代码 // 指针的用法 package main import ( "fmt" ) func main() { var i int = 100 // 输出i的地址 fmt.Prin ...
 - C++ typedef用法小结 (※不能不看※)
		
C++ typedef用法小结 (※不能不看※) 第一.四个用途 用途一: 定义一种类型的别名,而不只是简单的宏替换.可以用作同时声明指针型的多个对象.比如:char* pa, pb; // 这多数不 ...
 - typedef用法小结
		
typedef用法小结- - 注意:本文转自网络,版权归原作者所有. typedef typedef用法小结- - 这两天在看程序的时候,发现很多地方都用到typedef,在结构体定义,还有一些数组等 ...
 - const和typedef的常见用法详解
		
一.说说const 一般而言,const主要是用来防止定义的对象再次被修改,定义对象变量时要初始化变量. 常见用法如下: 1.用于定义常量变量,这样这个变量在后面就不可以再被修改 const int ...
 - 函数fgets和fputs、fread和fwrite、fscanf和fprintf用法小结  (转)
		
函数fgets和fputs.fread和fwrite.fscanf和fprintf用法小结 字符串读写函数fgets和fputs 一.读字符串函数fgets函数的功能是从指定的文件中读一个字符串到字符 ...
 - 结构体定义 typedef struct 用法详解和用法小结
		
typedef是类型定义的意思.typedef struct 是为了使用这个结构体方便.具体区别在于:若struct node {}这样来定义结构体的话.在申请node 的变量时,需要这样写,stru ...
 - [转载]typedef常见用法
		
注:本文系转载,并修改了一些错误. typedef常见用法 1.常规变量类型定义 例如:typedef unsigned char uchar描述:uchar等价于unsigned char类型定义 ...
 - MVC图片上传详解   IIS (安装SSL证书后) 实现 HTTP 自动跳转到 HTTPS  C#中Enum用法小结  表达式目录树  “村长”教你测试用例  引用provinces.js的三级联动
		
MVC图片上传详解 MVC图片上传--控制器方法 新建一个控制器命名为File,定义一个Img方法 [HttpPost]public ActionResult Img(HttpPostedFile ...
 
随机推荐
- ZOJ 2760 How Many Shortest Path(最短路径+最大流)
			
Description Given a weighted directed graph, we define the shortest path as the path who has the sma ...
 - JavaScript筑基篇(三)->JS原型和原型链的理解
			
删除理由:很久以前写的,当时理解不够深入,这样描述反而看起来更复杂了.因此就删掉,免得误人子弟! 可以看看另一篇文章:[如何继承Date对象?由一道题彻底弄懂JS继承.](http://www.cnb ...
 - 自测之Lesson10:管道
			
题目:建立双向管道,实现:父进程向子进程传送一个字符串,子进程对该字符串进行处理(小写字母转为大写字母)后再传回父进程. 实现代码: #include <stdio.h> #include ...
 - maven把项目打包成jar包后找不到velocity模板的bug
			
使用springmvc 开发时候要实现发送velcotiy模板邮件,在配置正常后,在本地测试正常后,使用maven打包成jar包后,报以下错误, Caused by: org.apache.veloc ...
 - 第一周—Fortran语言学习
			
使用教材:Fortran95程序设计[彭国伦] 第二章 编译器的使用 编译结果的好坏 1.翻译正确 2.执行文件的运行效率 3.翻译出来的执行码的长短 4.编译过程花费的时间 5.编译器提供Debug ...
 - Calculator 2
			
github地址:https://github.com/YooRarely/object-oriented.git 新增: 计算类(拥有计算功能) 采用符号优先级计算方法 对符号不匹配的如 -2 ,自 ...
 - 基本数据类型(int,bool,str)
			
目录: 1.int 数字类型 2.bool 布尔值 3.str 字符串类型 一.整型(int) 在python3中所有的整数都是int类型.但在python2中如果数据量 ...
 - keyboard shortcuts & Xcode 10
			
keyboard shortcuts & Xcode 10 Xcode Keyboard Shortcuts https://swifteducation.github.io/assets/p ...
 - java获取时间整点工具代码
			
/**获取上n个小时整点小时时间 * @param date * @return */ public static String getLastHourTime(Date date,int n){ C ...
 - matlab padarray
			
功能:填充图像或填充数组 使用:B = padarray(A,padsize,padval,direction) A表示输入图像,B是填充后的图像,padsize给出了填充的行数和列数,通常用[r c ...