文章目录:
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