指针
指针是什么
指针 是一种数据变量类型,它储存了一个寄存器地址,这个地址的值指向储存在该地址的对象的值。
名为 a 的指针,指向一个存储器地址,当中的值为 b。
图片来自Wikipedia.org
指针的优点
指针相对于值传递的速度更快(在相应变量占用储存空间较多的情况下),值传递在C/C++中是将已有对象复制一份到一个新的内存空间中;而指针则是给出储存数据的位置和数据类型,对数据直接进行操作。
定义指针变量
定义指针变量比较简单,如下
int *a; //定义整型指针变量a
char *b; //定义char类型指针变量
int **c; //定义(int *)类型指针变量,所谓的二级指针
char ***d; //定义(char **)类型的指针变量,所谓的三级指针
//特殊的指针,void型指针,可以指向任何数据类型
void* ptr = a; //OK 指向int类型的数据
ptr = b; //OK 指向char类型的数据
指针变量的值中有一个非常特殊的值: NULL,它不指向系统中的任何变量或者函数。一般,我们使用它作为一个标志(返回指针的函数没有正确执行、到达链表末尾等等)
指针的运算符
指针的常用运算符有两个,一个是 &,一个是 *
首先说说 &,它是取地址运算符(在这里它是单目运算符,区别于按位与),使用它可以得到相应变量的储存地址。
其次说说 *,它是指针运算符(或称间接访问运算符,区别于乘号),使用它可以得到指针指向空间的实际值,也可以修改指针指向空间的值。(需要区别于定义变量时,在变量名前写的星号)
举几个例子
int a = 0;
int *b = &a;
int **c = &b; //多级指针赋值
*b = 1; //a == 1, 完全等价于 a = 1;
*(&a) = 2; //a == 2, 完全等价于 a = 2;
&(*b) == b; //先间接访问a,再取出其地址, true
*(&a) == a; //先取a的地址,在通过指针间接访问a, true
"&*(接地址)"连用表示什么都没干
"*&(接变量名)"连用也表示什么都没干
const修饰
在定义指针的时候,可以使用const对其进行修饰,const修饰有两种写法
int a[2] = {1, 2};
const int *b = a; //不能修改指针指向内容的值
b++; //合法的
*b = 2; //非法的
int *const c = a; //不能修改指针变量的值
*c = 4; //合法的
c++; //非法的
两种const修饰也可以一起用
int a[2] = {1, 2};
const int *const b = a; //既不能修改指针变量的值,也不能修改指针指向内容的值
b++; //非法的
*b = 3; //非法的
coding环节
例一
观察下面两种方法的输出结果有何不同
例一值传递
#include <stdio.h>
void swap(int a, int b) {
int c = a;
a = b;
b = c;
}
int main() {
int a = 2, b = 3;
swap(a, b);
printf("a=%d\tb=%d\n", a, b);
return 0;
}
例一指针传递
#include <stdio.h>
void swap(int* pa, int* pb) {
int c = *pa;
*pa = *pb;
*pb = c;
}
int main() {
int a = 2, b = 3;
swap(&a, &b);
printf("a=%d\tb=%d\n", a, b);
return 0;
}
例一总结
值传递只是拷贝了一份变量的值到新的内存空间,对新内存空间中的变量进行操作,而不会对原有变量造成任何影响。指针操作则直接对原有变量内存空间中的数据进行了修改,从而正确地完成了交换数据的功能。
例二
对比两种方法的运行时间
例二值传递
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//定义一个占用空间比较多的变量类型
typedef struct {
int a;
float b;
double c;
double d;
double e;
double f;
double g;
double h;
} comp;
int main() {
comp a, b = {};
getchar(); //排除执行过程对运行时间产生的影响
clock_t start = clock(); //记录开始时间
int i = 0;
for (; i < 100000000; i++) {
b.a = i;
a = b; //每次执行此条语句都是将comp中的所有变量一个一个的从b赋值到a
}
clock_t end = clock(); //记录结束时间
printf("Used time=%dms\n", end - start);
return 0;
}
例二指针传递
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//定义一个占用空间比较多的变量类型
typedef struct {
int a;
float b;
double c;
double d;
double e;
double f;
double g;
double h;
} comp;
int main() {
comp *a, b = {};
getchar(); //排除编译过程对运行时间产生的影响
clock_t start = clock(); //记录开始时间
int i = 0;
for (; i < 100000000; i++) {
b.a = i;
a = &b; //只取出b的地址
}
clock_t end = clock(); //记录结束时间
printf("Used time=%dms\n", end - start);
return 0;
}
例二总结
在变量占用空间较多的情况下,采用指针传值会比普通的值传递快很多
总结1
上面的两个例子说明了在一些情况下,使用指针赋值来代替传统的赋值操作会让程序运行得更快
而且我们对指针的调用也能够实现对两个变量的值进行交换,或是读取相应变量的值。
而这时有人或许会对指针为什么还要有不同的类型有所疑问。下面就让我们尝试着运行一下下面
的这串代码。
#include <stdio.h>
int main() {
int array1[10];
char array2[10];
printf("数组元素中,第0个元素的地址 %d\n", &array1[0]);
printf("整型数组的首地址是 %d\n数组中第一个元素的地址是 %d\n", array1, &array1[1]);
//使用指针加1的效果:
printf("int型指针加1: %d\n", array1 + 1);
printf("字符数组的首地址是 %d\n数组中第一个元素的地址是 %d\n", array2, &array2[1]);
//使用指针加1的效果:
printf("char型指针加1:%d\n", array2 + 1);
return 0;
}
C\C++使用指针的类型来确定一次指针变量 +1 需要在内存空间向后移动多少个字节,同时
指针的类型也使得程序能够正确地完成指针–>值的转换(这里的"值"指的是指针指向的内存
空间中储存的值)。
其次,上面的程序也证明了数组的首地址与数组名中储存的地址相同!
下图是一个字符数组在内存空间内的存储结构图,对于字符数组,其指针每加“1”在内存空间内就是将指向的地址向后移动了一个字节(8位)的长度。
下图是一个整型数组在内存空间内的储存结构图,对于整型数组,其指针每加“1”在内存空间内就是将指向的地址向后移动了四个字节(32位)的长度。
二维数组的指针
运行一下试试
#include <stdio.h>
int main() {
int array[3][10];
printf("二维数组首地址 %d\n", array);
printf("第一行一维数组首地址 %d\n", *array);
printf("二维数组第二行地址 %d\n", array + 1);
printf("第二行一维数组首地址 %d\n", *(array + 1));
return 0;
}
是不是觉得二维数组的指针还是挺好理解的
那下面来看看提高部分吧
Error code
#include <stdio.h>
int main() {
int array[3][10] = {};
int **p = array;
printf("第1行第0列的值 %d\n", **(p1+1));
return 0;
}
好吧,这样居然就报错!我强制转换总行了吧!
#include <stdio.h>
int main() {
int array[3][10] = {};
int **p = (void**)array;
return 0;
}
还错,那怎样才正确嘛!
Correct code
#include <stdio.h>
int main() {
int array[10][10] = {};
int(*p1)[10] = array;
printf("第1行第0列的值 %d\n", **(p1+1));
return 0;
}
二维数组指针总结
二维数组指针和二级指针的差异来自于内存中储存数组方式的不同
理想的情况下,二维数组储存的方式如下图所示
而在计算机的内存中,考虑到内存空间的总量有限,二维数组是以下图的形式储存的
也就是说,在C\C++中,二维数组是一个伪二维数组,它本质上就是一个一维数组
函数指针
函数指针,函数指针,看名字就能看出来,它是一个指向函数的指针。定义方法为:
<Typename1> (*funcPointer)(<Typename2>, …)
Typename1 为函数返回值类型
Typename2… 为函数的形参表
下面提供一个使用函数指针的例子
例三
#include <stdio.h>
int func1(int a) {
return a / 2;
}
int func2(int a) {
return a * 2;
}
int main() {
int (*funcPointer)(int); //定义函数指针
funcPointer = func1;
printf("func1: %d\n", funcPointer(10)); //函数指针的调用
funcPointer = func2;
printf("func1: %d\n", funcPointer(10)); //函数指针的调用
return 0;
}
例三总结
函数指针是一个非常非常有用的工具,下面我们就举一个例子。
例四
在下面的这个例子中,我们将使用函数指针来指定数组的排序方法(升序 or 降序)
//请如果现在打开的是C语言的项目,请先新建一个C++的项目
#include <iostream>
#include <algorithm>
using namespace std;
int array[1000];
bool cmp(int front, int behind) { //比较函数(现在是升序)
return front < behind;
}
int main() {
int n;
//*****输入数组 Start******
cin >> n;
int i = 0;
for (; i < n; i++) {
cin >> array[i];
}
//****输入数组 End*********
sort(array, array+n, cmp); //传入比较函数,告诉sort这个数组从小到大排
for (i = 1; i < n; i++) { //测试数组排序是否正确
if (array[i] < array[i - 1]) {
cout << "Error!\n";
return -1;
}
}
return 0;
}
例五
使用指针进行数组元素交换
啥,怎么又是使用指针交换数组元素????
这次是数组元素万能交换~~~
#include <stdio.h>
void swp(void*, unsigned);
int main() {
int a[2] = {1, 2};
double b[2] = {2.4, 3.4};
swp(a, sizeof(int));
printf("int数组 %d %d\n", a[0], a[1]);
swp(b, sizeof(double));
printf("double数组 %f %f", b[0], b[1]);
return 0;
}
/*******万能数组元素交换器************
* 传入参数ptr(任意类型指针),size指针对应的变量所占用的字节数(可使用sizeof(类型名))
*/
void swp(void* ptr, unsigned size) {
char a;
unsigned i;
char *first, *second;
first = (char *)ptr;
second = first + size;
for (i = 0; i < size; i++) {
a = first[i];
first[i] = second[i];
second[i] = a;
}
}
//**************end*****************
例六
使用qsort对结构体进行排序
#include <stdlib.h>
#include <stdio.h>
int array[1000];
/**
* 比较函数类型 int funcName(const void *, const void *)
* 使用const修饰以避免对数组元素值的意外修改
*/
int cmp(const void *front, const void *behind) {
return *(const int *)front - *(const int *)behind; //将void *指针转换为int *(使用const修饰以避免对数组元素值的意外修改)
}
int main() {
int n;
scanf("%d", &n);
int i = 0;
for (; i < n; i++) {
scanf("%d", array + i);
}
qsort(array, n, sizeof(int), cmp); //传入四个参数,第一个是数组首地址,第二个是数组长度,第三个是数组中一个元素所占用的字节数,第四个是比较函数
for (i = 1; i < n; i++) {
if (array[i] < array[i-1]) {
printf("Error!\n"); //未正确排序,输出Error,并退出程序
return -1;
}
}
return 0;
}
总结2
指针作为一种高效的传递参数的变量类型,正确地使用可以加快程序运行速度。在C/C++中也有许多用到了指针的地方。例如:qsort,sort函数 cout操作符等。