指针与函数指针
前面一直都没有讲到指针的东西,但是昨天被小伙伴私信让我写一下指针
那今天就来写一些C/C++最有特色的东西–指针
要强调的是,在很多语言如Java当中,都没有指针的概念
那么C/C++的指针为我们提供了对内存区域的任意访问和读写
形象的说,内存每个字节就像是酒店里面房间,内存地址就是相应的房间编号,而指针存放着的就是房间号
指针的定义
int *p;
char *pc;
float *pf;
指针的内容
int * p=(int *)40000;//p只想地址40000,地址p就是地址40000
//*p代表就是40000开始处的若干字节
为什么是若干呢,在后面面慢慢道来
指针来访问内存空间
int *p=(int *)40000;
*p=5000;
int n=*p;
上面这三条语句什么意思呢,一张图就可以解释清楚了
那么我们看的出来,这里的若干=sizeof(int)
我们总结一下
p类型为 T*
*p类型为 T
*p可以从地址p开始的sizeof(T)个字节进行读写
而这一个星号就是间接引用运算符,sizeof(T*)为4字节(64位机上可能为8字节)
好啦,那么来看看指针的用法把。
- & : 取地址运算符
- &x : 变量x的地址(即指向x的指针)
- 对于类型为 T 的变量 x,&x 表示变量 x 的地址(即指向x的指针) &x 的类型是 T *。
example:
#include <iostream>
using namespace std;
int main()
{
char ch1 = 'A';
char * pc = &ch1; // 使得pc 指向变量ch1
*pc = 'B'; // 使得ch1 = 'B'
cout<<ch1<<endl;
char ch2 = * pc; // 使得ch2 = ch1
pc = & ch2; // 使得pc 指向变量ch2
*pc = 'D'; // 使得ch2 = 'D'
cout<<ch2<<endl;
}
指针的作用
-
有了指针,就有了自由访问内存空间的手段
-
不需要通过变量,就能对内存直接进行操作。通过指针,程序能访问的内存区域就不仅限于变量所占据的数据区域
-
在C++中,用指针p指向a的地址,然后对p进行加减操作,p就能指向a后面或前面的内存区域,通过p也就能访问这些内存区域
指针之间的相互赋值
注意:不同类型的指针,如果不经过强制类型转换,不能直接互相赋值
int * pn;
char * pc;
char c = 0x65;
//pn = pc; //类型不匹配,编译出错
//pn = & c; //类型不匹配,编译出错
pn = (int * ) & c;
int n = * pn; //n值不确定
* pn = 0x12345678; //编译能过但运行可能出错
cout<<n<<endl;
指针的运算
1) 两个同类型的指针变量,可以比较大小
地址p1<地址p2,p1< p2 值为真。
地址p1=地址p2,p1== p2 值为真
地址p1>地址p2,p1 > p2 值为真
2) 两个同类型的指针变量,可以相减
两个T * 类型的指针 p1和p2
p1 – p2 = ( 地址p1 – 地址 p2 ) / sizeof(T)
example:
int * p1, * p2;
若 p1 指向地址 1000,p2 指向地址 600, 则
p1 – p2 = (1000 – 600)/sizeof(int) = (1000 – 600)/4 = 100
3)指针变量加减一个整数的结果是指针
p+n : T * 类型的指针,指向地址:
地址p + n × sizeof(T)
类似的有n+p, p-n , *(p+n), *(p-n)
4) 指针变量可以自增、自减
- p++, ++p : p指向 n + sizeof(T)
- p–, --p : p指向 n - sizeof(T)
5)指针可以用下标运算符[ ]进行运算
p 是一个 T * 类型的指针,
n 是整数类型的变量或常量
p[n] 等价于 *(p+n)
提个小问题:
如何访问int型变量 a 前面的那一个字节?
我们通过强制类型转换来完成(int类型是4个字节的)
int a;
char * p = (char * ) &a; // &a是 int *类型
--p;
printf("%c", * p); //可能导致运行错误
* p = 'A'; //可能导致运行错误
为什么有可能运行错误呢?
∵那个位置可能是你没有办法访问的,本来就有东西在
下面上一个指针运算的代码示例
#include <iostream>
using namespace std;
int main() {
int * p1, * p2; int n = 4;
char * pc1, * pc2;
p1 = (int *) 100; //地址p1为100
p2 = (int *) 200; //地址p2为200
cout<< "1) " << p2 - p1 << endl;
//输出 1) 25, 因(200-100)/sizeof(int) = 100/4 = 25
pc1 = (char * ) p1; //地址pc1为100
pc2 = (char * ) p2; //地址pc2为200
cout<< "2) " << pc1 - pc2 << endl; //输出 2) -100
//输出 2) -100,因为(100-200)/sizeof(char) = -100
cout<< "3) " << (p2 + n) - p1 << endl; //输出 3) 29
int * p3 = p2 + n; // p2 + n 是一个指针,可以用它给 p3赋值
cout<< "4) " << p3 - p1 << endl; //输出 4) 29
cout<< "5) " << (pc2 - 10) - pc1 << endl; //输出 5) 90
return 0;
}
空指针和指针作为函数参数
- 地址0不能访问。指向地址0的指针就是空指针
- 可以用“NULL”关键字对任何类型的指针进行赋值。NULL实际上
就是整数0,值为NULL的指针就是空指针:
int * pn = NULL;
char * pc = NULL;
int * p2 = 0;
- 指针可以作为条件表达式使用。
- 如果指针的值为NULL,则相当于为 假,值不为NULL,就相当于为真 if§ , if(p!=NULL)
if(!p) ,if( p==NULL )
传参
#include <iostream>
using namespace std;
void Swap( int *p1, int * p2) {
int tmp = *p1; // 将p1指向的变量的值,赋给tmp
*p1 = *p2; // 将p2指向的变量的值,赋给p1指向的变量
*p2 = tmp; // 将tmp 的值赋给p2指向的变量。
}
int main()
{
int m = 3,n = 4;
Swap( &m, &n); //使得p1指向m,p2指向n
cout << m << " " << n << endl; //输出 4 3
return 0;
}
指针和数组
数组大家都很熟悉了,那么指针和数组又有什么亲密的关系呢
数组的名字是一个指针常量,指向数组的起始地址
T a[N];
- a的类型是 T *
- 可以用a给一个T * 类型的指针赋值
- a是编译时其值就确定了的常量,不能够对a进行赋值
*作为函数形参时, T p 和 T p[ ] 等价
示例代码
#include <iostream>
using namespace std;
int main() {
int a[200]; int * p ;
p = a; // p指向数组a的起始地址,亦即p指向了a[0]
* p = 10; //使得a[0] = 10
*( p + 1 ) = 20; //使得 a[1] = 20
p[0] = 30; //p[i] 和 *(p+i) 是等效的,使得a[0] = 30
p[4] = 40; //使得 a[4] = 40
for( int i = 0;i < 10; ++i) //对数组a的前10个元素进行赋值
*( p + i) = i;
++p; // p指向 a[1]
cout << p[0] << endl; //输出1 p[0]等效于*p, p[0]即是a[1]
p = a + 6; // p指向a[6]
cout << * p << endl;// 输出 6
return 0;
}
翻转数组输出:
#include <iostream>
using namespace std;
void Reverse(int * p,int size) { //颠倒一个数组
for(int i = 0;i < size/2; ++i) {
int tmp = p[i];
p[i] = p[size-1-i];
p[size-1-i] = tmp;
}
}
int main()
{
int a[5] = {1,2,3,4,5};
Reverse(a,sizeof(a)/sizeof(int));
for(int i = 0;i < 5; ++i) {
cout << *(a+i) << "," ;
}
return 0;
}
看完上面这些大家可能都感觉指针这种东西很繁琐,但是捏,它确实是非常有用的。
指针和二维数组
- 如果定义二维数组:T a[M][N];
- ai是一个一维数组a[i]的类型是 T *
- sizeof(a[i]) = sizeof(T) * N
- a[i]指向的地址: 数组a的起始地址 + i×N×sizeof(T)
其实吧,没有二维不二维的,都是一维数组,只不过数组里面每个元素都是数组,都是连续存储的
借助下面代码理解一下吧
void Reverse(int * p,int size) { //颠倒一个数组
for(int i = 0;i < size/2; ++i) {
int tmp = p[i];
p[i] = p[size-1-i];
p[size-1-i] = tmp;
} }
int a[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12}};
结果:
Reverse(a[1],4);
=> { {1,2,3,4},{8,7,6,5},{9,10,11,12}};
Reverse(a[1],6);
=> { {1,2,3,4},{10,9,5,6},{7,8,11,12}};
指向指针的指针
定义:
T ** p;
p是指向指针的指针,p指向的地方应该存放着一个类型为 T * 的指针
*p 的类型是 T *
示意图
示例代码
#include <iostream>
using namespace std;
int main()
{
int **pp; //指向int*类型指针的指针
int * p;
int n = 1234;
p = &n; // p指向n
pp = & p; //pp指向p
cout << *(*pp) << endl; // *pp是p, 所以*(*pp)就是n
return 0;
}
output:1234
指针和字符串
有一个很有趣的东西
- 字符串常量的类型就是 char *
- 字符数组名的类型也是 char *
先来看一段代码
#include <iostream>
using namespace std;
int main()
{
char * p = "Please input your name:\n";
cout << p ; // 若不用cout, printf(p) 亦可
char name[20];
char * pName = name;
cin >> pName;
cout << "Your name is " << pName;
return 0;
}
*字符数组名的类型也是 char ,就是一个地址
char name[20];
int n;
scanf("%d%s",&n, name);
//cin >> n >> name;
字符串部分的内容在我的上一篇博客有提到过,C/C++函数,位运算基础,字符串
1.char * strchr(const char * str,int c);
寻找字符c在字符串str中第一次出现的位置。如果找到,就返回指向该位置的char*指
针;如果str中不包含字符c,则返回NULL
2.char * strstr(const char * str, const char * subStr);
寻找子串subStr在str中第一次出现的位置。如果找到,就返回指向该位置的指针;如
果str不包含字符串subStr,则返回NULL
3.int stricmp(const char * s1,const char * s2);
大小写无关的字符串比较。如果s1小于s2则返回负数;如果s1等于s2,返回0;s1大
于s2,返回正数。不同编译器编译出来的程序,执行stricmp的结果就可能不同。
4.int strncmp(const char * s1,const char * s2,int n);
比较s1前n个字符组成的子串和s2前n个字符组成的子串的大小。若长度不足n,则取
整个串作为子串。返回值和strcmp类似。
5.char * strncpy(char * dest, const char * src,int n);
拷贝src的前n个字符到dest。如果src长度大于或等于n,该函数不会自动往dest中写
入‘\0’;若src长度不足n,则拷贝src的全部内容以及结尾的‘\0’到dest。
6.char * strtok(char * str, const char * delim);
连续调用该函数若干次,可以做到:从str中逐个抽取出被字符串delim中的字符分隔
开的若干个子串。
7.int atoi(char *s);
将字符串s里的内容转换成一个整型数返回。比如,如果字符串s的内容是“1234”,那
么函数返回值就是1234。如果s格式不是一个整数,比如是"a12",那么返回0。
8.double atof(char *s); 将字符串s中的内容转换成实数返回。比如,"12.34"就会转换成12.34。如果s的格式
不是一个实数 ,则返回0。
9.char *itoa(int value, char *string, int radix);
将整型值value以radix进制表示法写入 string:
char szValue[20];
itoa( 27,szValue,10); //使得szValue的内容变为 "27"
itoa( 27,szValue,16); //使得szValue的内容变为"1b"
示例代码
重点看看最后一个操作
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
char s1[100] = "12345";
char s2[100] = "abcdefg";
char s3[100] = "ABCDE";
strncat(s1,s2,3); // s1 = "12345abc"
cout << "1) " << s1 << endl; //输出 1) 12345abc
strncpy(s1,s3,3); // s3的前三个字符拷贝到s1,s1="ABC45abc"
cout << "2) " << s1 << endl; //输出 2) ABC45abc
strncpy(s2,s3,6); // s2 = "ABCDE"
cout << "3) " << s2 << endl; //输出 3) ABCDE
cout << "4) " << strncmp(s1,s3,3) << endl;
//比较s1和s3的前三个字符,比较结果是相等,输出 4) 0
char * p = strchr(s1,'B'); //在s1中查找 'B'第一次出现的位置
if( p ) // 等价于 if( p!= NULL)
cout << "5) " << p - s1 <<"," << *p << endl; //输出 5) 1,B
else
cout << "5) Not Found" << endl;
p = strstr( s1,"45a"); //在s1中查找字串 "45a"。s1="ABC45abc"
if( p )
cout << "6) " << p - s1 << "," << p << endl; //输出 6) 3,45abc
else
cout << "6) Not Found" << endl;
//以下演示strtok用法:
cout << "strtok usage demo:" << endl;
char str[] ="- This, a sample string, OK.";
//下面要从str逐个抽取出被" ,.-"这几个字符分隔的字串
p = strtok (str," ,.-"); //请注意," ,.-"中的第一个字符是空格
while ( p != NULL) { //只要p不为NULL,就说明找到了一个子串
cout << p << endl;
p = strtok(NULL, " ,.-"); //后续调用,第一个参数必须是NULL
}
return 0;
}
void 指针
可以用任何类型的指针对 void 指针进行赋值或初始化:
double d = 1.54;
void * p = & d;
void * p1;
p1 = & d;
因 sizeof(void) 没有定义,所以对于 void * 类型的指针p ∴*p 无定义;++p, --p, p += n, p+n,p-n 也没有无定义
内存操作库函数
头文件
<cstring>
void * memset(void * dest,int ch,int n); | 将从dest开始的n个字节,都设置成ch。返回值是dest |
---|---|
ch只有最低的字节起作用。
例:将szName的前10个字符,都设置成’a’:
char szName[200] = "";
memset( szName,'a',10);
cout << szName << endl;
memset有一个很常用的操作
用memset函数将数组内容全部设置成0:
int a[100];
memset(a,0,sizeof(a));
void * memcpy(void * dest, void * src, int n); | 将地址src开始的n个字节,拷贝到地址dest。返回值是dest。 |
---|---|
将数组a1的内容拷贝到数组a2中去,结果是a2[0] = a1[0], a2[1] = a1[1]……a2[9] = a1[9] :
int a1[10];
int a2[10];
memcpy( a2, a1, 10*sizeof(int));
如何自己编写一个memcpy
void * MyMemcpy( void * dest , const void * src, int n)
{
char * pDest = (char * )dest;
char * pSrc = ( char * ) src;
for( int i = 0; i < n; ++i ) {
//逐个字节拷贝源块的内容到目的块
* (pDest + i) = * ( pSrc + i );
}
return dest;
}
但是上面代码又不完美的地方,在dest区间和src区间有重叠时可能出问题!!!
当然库函数的memcpy和这个总体思路差不多,但是有一些细节进行了处理。
函数指针
当然函数指针非常好用,但是对于函数对象而言,可重用性还是差了一些。后面的STL博客当中会给大家介绍一下函数对象。
definition:
程序运行期间,每个函数都会占用一段
连续的内存空间。而函数名就是该函数所占
内存区域的起始地址(也称“入口地址”)。我
们可以将函数的入口地址赋给一个指针变量
,使该指针变量指向该函数。然后通过指针
变量就可以调用这个函数。这种指向函数的
指针变量称为“函数指针”。
example
int (*pf)(int ,char);
表示pf是一个函数指针,它所指向的函数,返回值类型应是int,该函数应有两个参数,第一个是int 类型,第二个是char类型。
可以用一个原型匹配的函数的名字给一个函数指针赋值。
要通过函数指针调用它所指向的函数,写法为:
函数指针名(实参表);
#include <stdio.h>
void PrintMin(int a,int b)
{
if( a<b )
printf("%d",a);
else
printf("%d",b);
}
int main() {
void (* pf)(int ,int);
int x = 4, y = 5;
pf = PrintMin;
pf(x,y);
return 0;
}
output:4
int CmpAsc(int x, int y)
{
//如果x>y返回1,否则返回0;
if(x>y)return 1;
else return 0;
}
void sort(int a[], int n, int (*cmp)(int,int))//最后一个参数为函数指针类型参数
{
/*对数组a排序,排序原则由cmp指定,若cmp为真,表示两元素逆序*/
for(int i=0;i<n-1;i++)
for(int j=i+1;j<n;j++)
if(cmp(a[i],a[j]))//调用传进来的函数指针
swap(a[i],a[j]);
}
int main() {
// insert code here...
int a[6] ={8,2,1,3,4,5};
sort(a, 6, CmpAsc);//CmpAsc函数名作为参数传递给函数sort
for(auto &e:a)
cout<<e<<" ";
return 0;
}
大家可以通过函数指针对排序自己写的基础算法进行一些操作
最后最后,给大家简单介绍一下库函数中的快熟排序
| void qsort(void *base, int nelem, unsigned int width,
int ( * pfCompare)( const void *, const void *)); | 可以对任意类型的数组进行排序 |
---|---|
void qsort(void *base, int nelem, unsigned int width, | |
int ( * pfCompare)( const void *, const void *)); | base: 待排序数组的起始地址,nelem: 待排序数组的元素个数,width: 待排序数组的每个元素的大小(以字节为单位)pfCompare :比较函数的地址 |
对数组排序,需要知道:
- 数组起始地址
- 数组元素的个数
- 每个元素的大小(由此可以算出每个元素的地址)
- 元素谁在前谁在后的规则
pfCompare: 函数指针,它指向一个“比较函数”。
该比较函数应为以下形式:
int 函数名(const void * elem1, const void * elem2);
比较函数是程序员自己编写的
原理上:qsort函数在执行期间,会通过pfCompare指针调用 “比较函数”,调用时将要比较的两个元素的地址传给“比较函数”,然后根据“比较函数”返回值判断两个元素哪个更应该排在前面。
比较函数编写规则:
- 如果 * elem1应该排在 * elem2前面,则函数返回值是负整数
- 如果 * elem1和* elem2哪个排在前面都行,那么函数返回0
- 如果 * elem1应该排在 * elem2后面,则函数返回值是正整数
下面给大家演示一下根据个位数大小,从小到大排序
#include <cstdio>
#include <cstdlib>
#define NUM 5
using namespace std;
int MyCompare( const void * elem1, const void * elem2 )
{
unsigned int * p1, * p2;
p1 = (unsigned int *) elem1; // “* elem1” 非法
p2 = (unsigned int *) elem2; // “* elem2” 非法
return (* p1 % 10) - (* p2 % 10 );
}
int main()
{
unsigned int an[NUM] = { 8,123,11,10,4 };
qsort( an,NUM,sizeof(unsigned int),MyCompare);
for( int i = 0;i < NUM; i ++ )
printf("%d ",an[i]);
return 0;
}
有什么问题,大家可以私信或者评论留言给我哦,祝大家节日快乐😁
学会程序和算法,走遍天下都不怕
贵州黄果树瀑布