10. 指针

10.1 指针的概念

1)每个变量都是存放在某个内存地址(以字节为单位)开始的若干个字节中.
2)指针也叫作指针变量,大小为4个字节(或8个)的变量,其内容代表一个内存地址.
3)通过指针,能够对该指针指向的内存区域进行读写.
4)如果把内存中的每个字节都想像成宾馆的一个房间,那么内存地址就相当于房间号,而指针里存放的就是房间号.
5)指针的定义:

类型名 * 指针变量名;
int* p;			//p是一个指针,变量p的类型是int*
char* pc;		//pc是一个指针,变量pc的类型是char*
float* pf;		//pf是一个指针,变量pf的类型是float*

6)指针的内容:

int* p = ( int* ) 40000;

7)p内容:
(1)十进制:40000
(2)十六进制:0x9c40
(3)二进制每个比特:0000 0000 0000 0000 1001 1100 0100 0000
(4)p指向地址40000,地址p就是地址40000
(5)*p就代表地址40000开始处的若干字节的内容

8)通过指针访问其指向的内存空间

int* p = ( int* ) 40000;
//往地址40000处起始的若干个字节的内存空间里写入5000
*p = 5000;
//将地址为4000处起始的若干字节的内容赋值给n
int n = *p;

9)若干 = sizeof(int),因为int* p.
10)指针定义总结:
(1)T * p; T可以是任何类型的名字,比如int、double、char
(2)p的类型为T*
(3)*p的类型为T
(4)通过表达式*p,可以读写从p开始的sizeof(T)个字节
(5)*p等价于存放在地址p处的一个T类型的变量
(6)*是简介引用运算符,sizeof(T*) = 4;(64位计算机可能是8字节)

10.2 指针的用法

1)&:取地址运算符

char ch1 = 'A';
char * pc = &ch1;	//使得pc指向变量ch1

2)对于类型为T的变量x,&x表示变量x的地址(即指向x的指针),&x的类型是T *.

char ch1 = 'A';
char * pc = &ch1;		//使得pc指向变量ch1
* pc = 'B';				//使得ch1 = 'B';
char ch2 = * pc;		//使得ch2 = ch1
pc = &ch2;				//使得pc指向变量ch2
* pc = 'D';				//使得ch2 = 'D'

3)指针的作用:
(1)不需要通过变量,就能对内存进行直接操作。通过指针,程序能访问的内存区域就不仅限于变量所占据的数据区域.、
(2)在C++中,用指针p指向a的地址,然后对p进行加减操作,p就能指向a后面或前面的内存区域,通过p也就能访问这些内存区域.

4)指针的相互赋值:不同类型的指针,如果不经过强制类型转换,不能直接相互赋值.

int * pn, char * pc, char c = 0x65;
pn = pc;	//类型不匹配,不能相互赋值
pn = &c;	//类型不匹配,编译出错
pn = (int*) &c;	//经过强制类型转换 正确
int n = * pn;	//n的值不确定 因为要在pn后面数四个字节 只有第一个字节是0x65 其他三个字节不确定
* pn = 0x12345678;	//编译没问题,但运行可能出错,因为c后面的3个字节不一定是什么情况

10.3 指针的运算

1)两个同类型的指针变量,可以比较大小.
(1)地址p1<地址p2 等价于 p1<p2;
(2)地址p1=地址p2 等价于 p1=p2;
(3)地址p1>地址p2 等价于 p1>p2;

2)两个同类型的指针变量,可以相减.
例如两个T * 类型的指针p1和p2
p1 - p2 = (地址p1 - 地址p2)/ sizeof(T);
例:int * p1,p2;
若p1指向地址为1000,p2执行地址为600,则
p1 - p2 = (1000 - 600)/ sizeof(T)= (1000 - 600)/ 4 = 100;

3)指针变量家见一个整数的结果还是指针:
例:
p :T *类型的指针
n:整数类型的变量或常量
p + n:T *类型的指针,指向地址p + n * sizeof(T)

4)指针变量可以自增自减.
例:
T *类型的指针p指向地址n
p++,++p:p指向n + sizeof(T)
p-- , --p:p指向n - sizeof(T)

5)指针可以用下标运算符"[ ]"进行运算.
例:
p :T *类型的指针
n:整数类型的变量或常量
p[n]等价于 *(p + n)

6)如何访问int型变量a前面的那一个字节?

int	a;
char * p = (char*) &a;	//&a是int*类型
--p;	
printf("%c", * p);		//可能导致运行错误	因为a前面的那个字节不知道什么情况
* p = 'A';				//可能导致运行错误

7)指针运算示例

#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;           //输出(200 - 100) / 4 = 25
    pc1 = (char *) p1;                          //地址pc1为100
    pc2 = (char *) p2;                          //地址pc2为200
    cout << "2) " << pc1 - pc2 << endl;         //输出(100 - 200) / 1 = -100
    cout << "3) " << (p2 + n) - p1 << endl;     //输出((200 + 4 * 4) - 100)/4 = 116/4 = 29
    int * p3 = p2 + n;                          //p2 + n是一个指针 地址为200 + 4 * 4 = 216
    cout << "4) " << p3 - p1 << endl;           //输出(216-100)/4 = 29
    cout << "5) " << (pc2 - 10) - pc1 << endl;  //输出((200 - 10 * 1) - 100)/1 = 90
    return 0;
}

8)空指针:地址0不能访问,指向地址0的指针就是空指针.
(1)可以用"NULL"关键字对指针进行赋值,NULL实际上就是整数0,值为NULL的指针就是空指针.

int * p = NULL;	char * pc = NULL;	int * p2 = 0;

(2)指针可以作为条件表达式使用,如果指针的值为NULL,则相当于为假,值不为NULL,就相当于为真.

if(p)		等价于 if(p != NULL)
if(!p)		等价于 if(p == NULL)

9)指针作为函数参数:

#include <iostream>
using namespace std;
void Swap( int * p1, int * p2){
    
    int temp = *p1;         //将p1指向变量的值,赋给temp
    *p1 = *p2;              //将p2指向的变量的值赋给p1指向的变量
    *p2 = temp;             //将temp的值赋给p2指向的变量
}

int main()
{
    int m = 3, n = 4;
    Swap( &m, &n);      //使得p1指向m,p2指向n
    cout << m << "  " << n <<endl;
    return 0;
}

10.4 指针和数组

10.4.1指针和一维数组:

1)数组的名字是一个指针常量,指向数组的其实地址
2)T a[N];
3)a的类型是T *
4)可以用a给一个T *类型的指针赋值
5)a编译时其值就确定为一个常量,不能对a进行赋值
6)作为函数的形参时,T * p和T p[]等价,下面两种形式等价.

void Func(int * p){cout << sizeof(p) << endl;}  //输出一个指针所占用的字节数4  
void Func(int p[]){cout << sizeof(p) << endl;} 	//输出一个指针所占用的字节数4  

7)指针和数组应用举例1:

#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;                      //使得p[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;            //输出a[6],即6
    return 0;
}

8)指针和数组应用举例2:颠倒一个数组

#include <iostream>
using namespace std;
void Reverse( int * p, int length){ //颠倒一个数组
    for (int i = 0; i < length/2; i++){
        int temp = p[i];
        p[i] = p[length - 1 - i];
        p[length - 1 - i] = temp;
    }
}
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] << " ";
    }
}

10.4.2 指针和二维数组

1)定义一个维维数组:T a[M][N]
(1)a[i]是一个一维数组
(2)a[i]的类型是T*
(3)sizeof(a[i]) = sizeof(T)* N
(4)a[i]指向的地址:数组a的起始地址 + i * N * sizeof(T)
(5)a和a[0]的地址是一样的

#include <iostream>
using namespace std;

int main()
{
    int a[3][2] = {1,2,3,4,5,6};
    cout << a <<endl;
    cout << a[0] <<endl;
    cout << a[1] <<endl;
    cout << a[2] <<endl;
}
输出:
0x6afee8
0x6afee8
0x6afef0
0x6afef8

2)指针和二维数组举例:颠倒一个数组

#include <iostream>
using namespace std;
void Reverse( int * p, int length){ //颠倒一个数组
    for (int i = 0; i < length/2; i++){
        int temp = p[i];
        p[i] = p[length - 1 - i];
        p[length - 1 - i] = temp;
    }
}
int main()
{
    int a[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
    Reverse(a[1], 4);
    for (int i = 0; i < 12; i++){
        cout << *(a[0] + i) << " ";
    }
    cout << endl;
    Reverse(a[1], 6);
    for (int i = 0; i < 12; i++){
        cout << *(a[0] + i) << " ";
    }
}
输出:
1 2 3 4 8 7 6 5 9 10 11 12
1 2 3 4 10 9 5 6 7 8 11 12

3)指向指针的指针
(1)定义:T ** p;
(2)p是指向指针的指针,p指向的地方应该存放着一个T *的指针
(3)*p的类型是 T *

#include <iostream>
using namespace std;
int main()
{
    int ** pp;  //pp是指向指针的指针
    int * p;    //p是指针
    int n = 1234;
    p = & n;    //p指向n
    pp = & p;   //pp指向p
    cout << *(*pp) << endl; //*pp是p,所以*(*pp)就是n
}

10.5 指针和字符串

1)字符串常量的类型就是char*
2)字符数组的类型也是char*,就是一个地址

#include <iostream>
using namespace std;
int main()
{
    char * p = "Please input your name:\n";
    cout << p << endl;
    char name[20];
    char * pName = name;    //pName指向name[0]
    cin >> pName;       
    cout << "Your name is:" << pName << endl;
    return 0;
}
输入:jack
输出:"Your name is:jack

3)常用的字符串操作库函数:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

10.6 void指针

1)void指针声明:void * p;
2)可以用任何类型指针对void指针初始化:

double d = 1.54;
void *  p = &d;		//使用double类型变量初始化p
void * p1;
p1 = &d;

3)因为sizeof(void)没有意义,所以对于void * 类型的指针p , *p无意义,++p、–p、p+=n等均无意义.
4)void指针使用1:内存操作库函数memset函数
(1)在头文件cstring中声明
(2)声明:void * memset(void * dest, int ch, int n);
(3)作用:将从dest开始的第n个字节,都设置成ch,返回值是dest,ch只有最低的字节起作用(0-255)
(4)举例1:

char szName[200] = "";
memset(szName, 'a', 10);
cout << szName << endl;
输出:aaaaaaaaaa

(5)举例2:将数组内容全部置为0

int a[200];
memset(a, 0, sizeof(a));

5)void指针使用1:内存操作库函数memcpy函数
(1)在头文件cstring中声明
(2)声明:void * memcpy(void * dest,void * src , int n);
(3)作用:将从src开始的n个字节,拷贝到地址dest,返回值是dest
(4)举例1:将数组a1的内容拷贝到a2中去

int a1[10];
int a2[10];
memcpy( a2, a1, 10);

10.7 函数指针

1)基本概念:程序运行期间,每个函数都会占用一段连续的内存空间。而函数名就是该函数所占连续内存区域的起始地址(也加“入口地址”)。我们可以将函数的入口地址赋给一个指针变量,使该指针变量指向该函数。然后通过指针变量就可以调用这个函数。这种指向函数的指针变量称为"函数指针".
2)定义形式:类型名 (*指针变量名)(参数类型1, 参数类型2…);

int (*pf)(int, char);

3)表示pf是一个函数指针,它所指向得函数返回值是int型,该函数有两个参数,第一个是int型,第二个是char类型
4)使用方法:可以用一个原型匹配的函数的名字给一个函数指针赋值,使用方法为:函数指针名(实参表).

#include <iostream>
using namespace std;
void PrintMin(int a, int b){
    if (a > b)
        cout << b <<endl;
    else
        cout << a << endl;
}
int main()
{
    void (*pf)(int, int);   //定义一个函数指针pf
    pf = PrintMin;          //通过函数名给函数指针赋值
    pf(4,5);                //通过 函数指针(参数列表)调用函数
}
输出:4

5)函数指针和qsort库函数:
(1)qsort库函数声明:void qsort(void *base, int nelem, unsigned int width, int (*pfCompare)(const void * elem1, const void * elem2));
(2)qsort函数可以对任意类型的数组排序
(3)参数列表:
base:数组的起始地址
nelem:数组的元素个数
width:每个元素所占字节数
pfCompare:指定排序规则的函数指针
(4)排序规则:
如果*elem1应该排在*elem2的前面,则函数返回值是负整数
如果*elem1应该排在*elem2的后面,则函数返回值是正整数
如果*elem1和*elem2哪个在前哪个在后都可以,则函数返回值是0

6)实例:将一个unsigned int类型的数组按照个位数从小到大排序

#include <iostream>
#include <cstdlib>		//qsort函数头文件
using namespace std;
int Compare(const void * elem1, const void * elem2){
    unsigned int * p1, * p2;
    p1 = (unsigned int *)elem1;
    p2 = (unsigned int *)elem2;
    return *p1%10 - *p2%10;
}
int main()
{
    int nums[4] = {18, 64, 23, 10};
    qsort(nums,4,4,Compare);
    for (int i = 0; i < 4; i++){
        cout << nums[i] << " ";
    }

}
输出:10 23 64 18
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值