目录

  • 指针相关概念
  • 指针变量
  • null指针
  • 指针的算术运算
  • 指针数组
  • 指向指针的指针
  • 传递指针给函数
  • 从函数返回指针

指针相关概念

变量

如果在程序中定义了一个变量,在对程序进行编译时,系统就会为这个变量分配内存单元。编译系统根据程序中定义的变量类型分配一定长度的空间。内存的基本单元是字节,一字节有8位。每字节都有一个编号,这个编号就是“地址”。

直接访问变量

在程序中一般是通过变量名来对内存单元进行存取操作的。其实程序经过编译以后已经将变量名转换为变量的地址,对变量值的存取都是通过地址进行的。这种按变量地址存取变量的方式称为直接访问方式。

间接访问变量

间接访问的方式,即变量中存放的是另一个变量的地址。也就是说,变量中存放的不是数据,而是数据的地址。就

按C语言的规定,可以在程序中定义整型变量、实型变量、字符型变量,也可以定义这样一种特殊的变量,它是存放地址的。

指针

数据在内存中的地址也称为指针,如果一个变量存储了一份数据的指针,我们就称它为指针变量。

在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。指针变量的值就是某份数据的地址,这样的一份数据可以是数组、字符串、函数,也可以是另外的一个普通变量或指针变量。

现在假设有一个 char 类型的变量 c,它存储了字符 'K'(ASCII码为十进制数 75),并占用了地址为 0X11A 的内存(地址通常用十六进制表示)。另外有一个指针变量 p,它的值为 0X11A,正好等于变量 c 的地址,这种情况我们就称 p 指向了 c,或者说 p 是指向变量 c 的指针。如下图:

“指针”和“指针变量”的区别:指针是一个地址;而指针变量是存放地址的变量。

通常也将“指针变量”简称为“指针”。

指针变量

概念

一个变量专门用来存放另一个变量的地址,那么就称它为“指针变量”。即指针变量里面存放的是指针,即地址。

指针变量的定义

语法格式:

基类型  *指针变量名;

 

基类型:数据类型和*组合再一起构成指针类型变量类型。

不同数据类型在内存中所占用的字节数是不同的,指针一般指向数据所在内存的第一个字节地址。

指针的运算中,地址加减的基本单位是数据类型的字节数。例如整数的指针变量+1,实际上在内存中是移动4个字节。

示例代码:

#include <stdio.h>

int main ()
{
int *p, *q;
int k; //k用来存放两个地址数相减的结果
int i = 3, j = 4;
p = &i;
q = &j;
k = p-q;
printf("p内存地址:%#x\nq内存地址:%#x\n地址间隔:k = %d\n", p, q, k);
return 0;
}

示例代码说明:

  • #x表示输出16进制数据,并且数据前添加0x。
  • &为取地址符。指针变量p和q的值为内存地址,需要使用取地址符号&。
  • 定义指针变量时必须带*,给指针变量赋值时不能带*。

输出结果:

修改指针变量的值

和普通变量一样,指针变量也可以被多次写入。

示例代码:

#include <stdio.h>

int main ()
{
//定义普通变量
float a = 99.5, b = 10.6;
char c = '@', d = '#';
//定义指针变量
float *p1 = &a;
char *p2 = &c;
//修改指针变量的值
p1 = &b;
p2 = &d;
return 0;
}

  

示例说明:

注意:p1、p2 的类型分别是float*和char*,而不是float和char,它们是完全不同的数据类型。如果让p1指向char*,p2指向float*,结果如何?

结果:指针指向的数据类型不相关,编译出错:

Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast

  

通过指针变量取得数据

指针变量存储了数据的地址,通过指针变量能够获得该地址上的数据。

语法格式:

*pointer;

  

这里的*称为指针运算符,用来取得某个地址上的数据。

示例代码:

#include <stdio.h>

int main(){
int a = 15;
int *p = &a;
printf("%d, %d\n", a, *p); //两种方式都可以输出a的值
return 0;
}

  

输出结果:

分析:示例中,假设 a 的地址是 0X1000,p 指向 a 后,p 本身的值也会变为 0X1000,*p 表示获取地址 0X1000 上的数据,也即变量 a 的值。从运行结果看,*p 和 a 是等价的。

CPU 读写数据必须要知道数据在内存中的地址,普通变量和指针变量都是地址的助记符,虽然通过 *p 和 a 获取到的数据一样,但它们的运行过程稍有不同:a 只需要一次运算就能够取得数据,而 *p 要经过两次运算,多了一层“间接”。

即:用指针是间接获取数据,使用变量名是直接获取数据,前者比后者的代价要高。

NULL 指针

在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。

赋为 NULL 值的指针被称为空指针。

NULL 指针是一个定义在标准库中的值为零的常量。

示例代码:

在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。

然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。

但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。

指针的运算

C 指针是一个用数值表示的地址。因此,可以对指针执行算术运算:++、--、+、-。

指针支持关系运算,如 ==、< 和 >。如果 p1 和 p2 指向两个相关的变量,比如同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较。

假设 ptr 是一个指向地址 1000 的整型指针,是一个 32 位的整数,让我们对该指针执行下列的算术运算:

ptr++; //移动到下一个位置
ptr--; //移动到上一个位置
ptr1==ptr2; //是否指向同一个地址

说明:  

在执行完ptr++后,ptr 将指向位置 1004,因为 ptr 每增加一次,它都将指向下一个整数位置,即当前位置往后移 4 个字节。

指针数组

定义

定义用来存储指针的数组。

语法格式:

数据类型  *指针数组变量名[数组大小]

  

例如:

int *ptr[100];

  

如上示例,把 ptr 声明为一个数组,由 100个整数指针组成。因此,ptr 中的每个元素,都是一个指向 int 值的指针。

示例:将整数保存在整数指针数组中

#include <stdio.h>

const int MAX = 3;//常量

int main ()
{
int nums[] = {10, 100, 200};
int *ptr[MAX];
int i;
for ( i = 0; i < MAX; i++)
{
ptr[i] = &nums[i]; /* 赋值为整数的地址 */
}
for ( i = 0; i < MAX; i++)
{
printf("数组元素nums[%d] = %d\n", i, *ptr[i] );
}
return 0;
}

  

输出结果:

示例:指向字符的指针数组来存储一个字符串列表

#include <stdio.h>

const int MAX = 4;

int main ()
{
const char *names[] = {
"张三",
"李四",
"王五",
"赵六",
};
int i = 0; for ( i = 0; i < MAX; i++)
{
printf("数组元素names[%d] = %s\n", i, names[i] );
}
return 0;
}

  

输出结果:

指向指针的指针

概念

指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。

通常,一个指针包含一个变量的地址。

当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。

如下图所示:

语法格式:

datatype  **ptr

  

同样,当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符。

示例代码

#include <stdio.h>

int main ()
{
int var;
int *ptr;
int **pptr; var = 3000; /* 获取 var 的地址 */
ptr = &var; /* 使用运算符 & 获取 ptr 的地址 */
pptr = &ptr; /* 使用 pptr 获取值 */
printf("var的值为 = %d\n", var );
printf("*ptr指向的值为 = %d\n", *ptr );
printf("**pptr指向的值为 = %d\n", **pptr); return 0;
}

输出结果:

传递指针给函数

指针作为函数参数

C 语言支持传递指针给函数,只需将函数参数声明为指针类型即可。

示例代码:

#include <stdio.h>
#include <time.h> void getSeconds(unsigned long *par); int main ()
{
unsigned long sec; getSeconds( &sec );//实参为sec的地址 /* 输出实际值 */
printf("Number of seconds: %ld\n", sec ); return 0;
} void getSeconds(unsigned long *par)
{
/* 获取当前的时间戳 */
*par = time( NULL );
return;
}

  

数组作为函数参数

指针能够作为函数参数,也能将数组指针作为参数。

对于一维数组而言,数组名就是指向该数组首地址的指针。

示例代码:

#include <stdio.h>

/* 函数声明 */
double getAverage(int *arr, int size); int main ()
{
/* 带有 5 个元素的整型数组 */
int balance[5] = {1000, 2, 3, 17, 50};
double avg;//平均值
/* 传递一个指向数组的指针作为参数 */
avg = getAverage( balance, 5 ) ; /* 输出返回值 */
printf("Average value is: %f\n", avg ); return 0;
} double getAverage(int *arr, int size)
{
int i, sum = 0;
double avg; for (i = 0; i < size; ++i)
{
sum += arr[i];
} avg = (double)sum / size; return avg;
}

  

从函数返回指针

C 允许从函数返回指针。为了实现该,必须声明一个返回指针的函数,语法格式如下:

int * myFunction()
{
//函数体
}

 

此外,C 语言不支持在调用函数时返回局部变量的地址,除非定义局部变量为 static 变量。

示例:

示例函数会生成 10 个随机数,并使用表示指针的数组名(即第一个数组元素的地址)来返回它们。

#include <stdio.h>
#include <time.h>
#include <stdlib.h> /* 要生成和返回随机数的函数 */
int * getRandom( )
{
static int r[10];
int i; /* 设置种子 */
srand( (unsigned)time( NULL ) );
for ( i = 0; i < 10; ++i)
{
r[i] = rand();
printf("%d\n", r[i] );
} return r;
} /* 要调用上面定义函数的主函数 */
int main ()
{
/* 一个指向整数的指针 */
int *p;
int i; p = getRandom();
for ( i = 0; i < 10; i++ )
{
printf("*(p + [%d]) : %d\n", i, *(p + i) );
} return 0;
}

  

输出结果:

C09 指针的更多相关文章

  1. TODO:Golang指针使用注意事项

    TODO:Golang指针使用注意事项 先来看简单的例子1: 输出: 1 1 例子2: 输出: 1 3 例子1是使用值传递,Add方法不会做任何改变:例子2是使用指针传递,会改变地址,从而改变地址. ...

  2. enote笔记法使用范例(2)——指针(1)智能指针

    要知道什么是智能指针,首先了解什么称为 “资源分配即初始化” what RAII:RAII—Resource Acquisition Is Initialization,即“资源分配即初始化” 在&l ...

  3. C++虚函数和函数指针一起使用

    C++虚函数和函数指针一起使用,写起来有点麻烦. 下面贴出一份示例代码,可作参考.(需要支持C++11编译) #include <stdio.h> #include <list> ...

  4. C++11 shared_ptr 智能指针 的使用,避免内存泄露

    多线程程序经常会遇到在某个线程A创建了一个对象,这个对象需要在线程B使用, 在没有shared_ptr时,因为线程A,B结束时间不确定,即在A或B线程先释放这个对象都有可能造成另一个线程崩溃, 所以为 ...

  5. c 数组与指针的使用注意事项

    数组变量和指针变量有一点小小的区别 所以把数组指针赋值给指针变量的时候千万要小心 加入把数组赋值给指针变量,指针变量只会包含数组的地址信息 而对数组的长度一无所知 相当于指针丢失了一部分信息,我们把这 ...

  6. Marshal.Copy将指针拷贝给数组

    lpStatuss是一个UNITSTATUS*的指针类型实例,并包含SensorDust字段 //定义一个数组类型 byte[] SensorDust = new byte[30] //将指针类型拷贝 ...

  7. C++智能指针

    引用计数技术及智能指针的简单实现 基础对象类 class Point { public: Point(int xVal = 0, int yVal = 0) : x(xVal), y(yVal) { ...

  8. EC笔记:第三部分:17、使用独立的语句将newed对象放入智能指针

    一般的智能指针都是通过一个普通指针来初始化,所以很容易写出以下的代码: #include <iostream> using namespace std; int func1(){ //返回 ...

  9. 智能指针shared_ptr的用法

    为了解决C++内存泄漏的问题,C++11引入了智能指针(Smart Pointer). 智能指针的原理是,接受一个申请好的内存地址,构造一个保存在栈上的智能指针对象,当程序退出栈的作用域范围后,由于栈 ...

随机推荐

  1. [UE4]如何替换角色Mesh上的Material材质

    http://www.dawnarc.com/2016/10/ue4%E5%A6%82%E4%BD%95%E6%9B%BF%E6%8D%A2%E8%A7%92%E8%89%B2mesh%E4%B8%8 ...

  2. U3D Shader基础

    http://blog.sina.com.cn/s/blog_471132920101dcnr.html 转载自风宇冲Unity3D教程学院 一个SubShader(渲染方案)是由一个个Pass块来执 ...

  3. [Xcode 实际操作]六、媒体与动画-(7)遍历系统提供的所有滤镜

    目录:[Swift]Xcode实际操作 本文将演示系统到底提供了多少滤镜供开发者使用,并了解每个滤镜都有哪些参数需要配置. 在项目导航区,打开视图控制器的代码文件[ViewController.swi ...

  4. 洛谷 P2895 [USACO08FEB]流星雨Meteor Shower 解题报告

    一起来看流星雨吧(话说我还没看到过流星雨呢) 题目 Problem 小A则听说另一个骇人听闻的消息: 一场流星雨即将袭击整个霸中,由于流星体积过大,它们无法在撞击到地面前燃烧殆尽,届时将会对它撞到的一 ...

  5. JS高级学习历程-5

    [闭包] 定义:闭包就是一个函数 条件:一个函数去嵌套另外一个函数,里边的函数就是闭包 function  f1(){ function f2(){ } } 特点:闭包函数有权访问父级环境的变量信息.

  6. js 去除字符串空白符

    var a="      123456" varb=a.replace(/(^\s*)/g, "");

  7. reaver 破解wifi

    1. 无线网卡设置为监控模式 airmon-ng start wlan0 2. 获取附近路由信息 airodump-ng wlan0mon 3. 使用reaver破解wifi reaver -i wl ...

  8. (转)COBBLER无人值守安装

    COBBLER无人值守安装 说在最前面的话 在看Cobbler之前请大家先看一下Kickstart无人值守安装,了解一下Cobbler的实现原理.但是Cobbler是独立的,不需要先安装Kicksta ...

  9. (转) cocos 里面scrollView一些方法

    void setBounceEnabled (bool enabled)设置当滚动到边界时,是否内部容器发生弹回(bounce)效果 bool isBounceEnabled () const获取边界 ...

  10. Redis的数据类型(lists、Sets)

    lists类型 Redis 列表是简单的字符串列表,按照插入顺序排序.你可以添加一个元素到列表的头部(左边)或者尾部(右边) LPUSH 命令插入一个新的元素到头部, 而 RPUSH 插入一个新元素导 ...