为什么要使用指针
函数的值传递,无法通过调用函数,来修改函数的实参;被调用函数需要提供更多的“返回值”给调用函数;减少值传递时带来的额外开销,提高代码执行效率
指针定义:指针是什么
int age=18;
/* 定义了一个指针,指针本身也是一个变量,名称是 p,
它是一个指针,可以指向一个整数。
也就是说: p 的值就是一个整数的地址!!! */
int *p ;
//指针 p 指向了 age,p 的值,就是 age 的地址
p = &age;
指针的定义
int *p; // int *p1, *p2;
int* p; // int* p1,p2; //p1 是指针, p2 只是整形变量
int * p;
int*p; //不建议
指针的初始化、访问
指针的初始化
int room = 2;
//定义两个指针变量指向 room
int *p1 = &room;
int *p2 = &room;
printf("room 地址:%p\n", &room);
printf("p1 地址:%p\n", &p1);
printf("room 所占字节:%d\n", sizeof(room));
printf("p1 所占字节:%d\n", sizeof(p1));
注意: 32 位系统中,int 整数占 4 个字节,指针同样占 4 个字节。64 位系统中,int 整数占 4 个字节,指针占 8 个字节
%p、0x%x、0x%X//使用 16 进制打印,把地址值当成一个无符号数来处理。
指针的访问
int room = 2 ;
int * girl = &room;
int x = 0;
x = *girl;
// & 是取地址符
// *是一个特殊的运算符,*girl 表示读取指针 girl 所指向的
//变量的值, *girl 相当于 room
空指针和野指针
空指针:就是值为 0 的指针。(任何程序数据都不会存储在地址为 0 的内存块中,它是被操作系
统预留的内存块。)
指针初始化为空指针: int *p = 0; 或者 int *p = NULL; //强烈推荐,目的就是,避免访问非法数据
指针不再使用时,可以设置为空指针。
int *select = &xiao_long_lv;
//和小龙女约会
select = NULL;
表示这个指针还没有具体的指向,使用前进行合法性判断
int *p = NULL;
// 。。。。
if (p) { //p 等同于 p!=NULL
//指针不为空,对指针进行操作
}
野指针指:指向无效内存地址的指针,通常是由于指针未被初始化、指向已释放的内存,或越界访问导致的。
//情形一 没有初始化
int *select;
//情形二 内存不确定
select = 100;
printf("选择的房间是: %d\n", *select);
const修饰使用:渣值暖超
#include <stdio.h>
#include <stdlib.h>
//const 和指针
int main(void){
int wife = 24;
int girl = 18;
//第一种 渣男型
int * zha_nan = &wife;
*zha_nan = 25;
zha_nan = &girl;
*zha_nan = 19;
printf("girl : %d,wife: %d\n", girl, wife);
//第二种 直男型 不能修改指向变量的值
//const int * zhi_nan = &wife; //第一种写法
int const * zhi_nan = &wife; // 第二种写法
*zhi_nan = 26;
printf("直男老婆的年龄:%d\n", *zhi_nan);
zhi_nan = &girl;
printf("直男女朋友的年龄:%d\n", *zhi_nan);
//*zhi_nan = 20;
//第三种 暖男型 不允许指向别的地址
int * const nuan_nan = &wife;
*nuan_nan = 26;
printf("暖男老婆的年龄:%d\n", wife);
//nuan_nan = &girl;
//第四种超级暖男型 不允许指向别的地址,不能修改指向变量的值
const int * const super_nuan_nan = &wife;
//*super_nuan_nan = 28;
//super_nuan_nan = &girl;
system("pause");
return 0;
}
总结: 看 const 离类型(int)近,还是离指针变量名近,离谁近,就修饰谁,谁就不能变
指针的算术运算
指针自增运算
p++ 是在 p 当前地址的基础上 ,自增 p 对应类型的大小,也就是说 p = p+ 1*(sizeof(类型))
指针自减运算
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*
* 让用户输入一个字符串,然后反向输出,注意:不能改变原来的字符串!
* 如: "12345" 逆转成
"54321" 输出
*/
int main(void)
{
char input[128];
int len;
char tmp;
scanf_s("%s", input, 128);
len = strlen(input);
//方法 1 交换
/* for( int i=0; i<len/2; i++)
{
tmp = input[i];
input[i] = input[len-i-1];
input[len-i-1] = tmp;
}*/
/*for(int i=0; i<len; i++)
{
printf("%c", input[i]);
}*/
//printf("逆转后:%s\n", input);
//第二种方法
/*for(int i=0; i<len; i++){
printf("%c", input[len-i-1]);
}
printf("\n");
*/
//第三种方法
char *p = &input[len-1];
for(int i=0; i<len; i++){
printf("%c", *p--);
}
printf("\n");
system("pause");
return 0;
}
指针与整数之间的加减运算
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void){
int ages[] = {20,18,19,24,23,28,30,38, 35, 32};
int len = sizeof(ages) / sizeof(ages[0]);
int *p = ages;
printf("第 7 个的年龄: %d\n", *(p+6));//30
printf("*p+6 = : %d\n", *p+6);//26
printf("第 3 个的年龄: %d\n", *(p+2));
int *p1 = &ages[4];
printf("相对于第 5 个,她的前一位的年龄: %d\n", *(p1 -1));
printf("相对于第 5 个,她的前三位的年龄: %d\n", *(p1 -3));
system("pause");
return 0;
}
指针与整数的运算,指针加减数字表示的意义是指针在数组中位置的移动;对于整数部分而言,它代表的是一个元素,对于不同的数据类型,其数组的元素占用的字节是不一样的,比如指针 + 1,并不是在指针地址的基础之上加 1 个地址,而是在这个指针地址的基础上加 1 个元素占用的字节数:如果指针的类型是 char*,那么这个时候 1 代表 1 个字节地址;如果指针的类型是 int*,那么这个时候 1 代表 4 个字节地址;如果指针的类型是 float*,那么这个时候 1 代表 4 个字节地址;如果指针的类型是 double*,那么这个时候 1 代表 8 个字节地址。
通用公式: 数据类型 *p;p + n 实际指向的地址:p 基地址 + n * sizeof(数据类型);p - n 实际指向的地址:p 基地址 - n * sizeof(数据类型)
比如:对于 int 类型,比如 p 指向 0x0061FF14,则:p+1 实际指向的是 0x0061FF18,与 p 指向的内存地址相差 4 个字节;p+2 实际指向的是 0x0061FF1C,与 p 指向的内存地址相差 8 个字节
对于 char 类型,比如 p 指向 0x0061FF28,则:p+1 实际指向的是 0x0061FF29,与 p 指向的内存地址相差 1 个字节;p+1 实际指向的是 0x0061FF2A,与 p 指向的内存地址相差 2 个字节;
指针与指针之间的加减运算
知识点:指针和指针可以做减法操作,但不适合做加法运算;指针和指针做减法适用的场合:两个指针都指向同一个数组,相减结果为两个指针之间的元素数目,而不是两个指针之间相差的字节数。
//比如:
int int_array[4] = {12, 34, 56, 78};
int *p_int1 = &int_array[0];
int *p_int2 = &int_array[3];
//p_int2 - p_int1 的结果为 3,即是两个之间之间的元素数目为 3 个。
如果两个指针不是指向同一个数组,它们相减就没有意义。不同类型的指针不允许相减,比如
char *p1;int *p2;p2-p1 是没有意义的。
二级指针
二级指针也是一个普通的指针变量,只是它里面保存的值是另外一个一级指针的地址
定义:
int guizi1 = 888;
//1 级指针,保存 guizi1 的地址
int *guizi2 = &guizi1;
//2 级指针,保存 guizi2 的地址,guizi2 本身是一个一级指针变量
int **liujian = &guizi2;
二级指针的用途:
1. 普通指针可以将变量通过参数“带入”函数内部,但没办法将内部变量“带出”函数
2. 二级指针可以不但可以将变量通过参数函数内部,也可以将函数内部变量 “带出”到函数外部。
#include <stdio.h>
#include <stdlib.h>
void swap(int *a, int *b){
int tmp =*a;
*a= *b;
*b= tmp;
}
void boy_home(int **meipo){
static int boy = 23;
*meipo = &boy;
}
int main(void){
//int x=10, y=100;
//swap(&x, &y);
//printf("x=%d, y=%d\n", x, y);
int *meipo = NULL;
boy_home(&meipo);
printf("boy: %d\n", *meipo);
system("pause");
return 0;
}
多级指针的定义、使用
可以定义多级指针指向次一级指针
//比如:
int guizi1 = 888;
int *guizi2 = &guizi1;
//普通指针
int **guizi3 = &guizi2;
//二级指向一级
int ***guizi4 = &guizi3;
//三级指向二级
int ****guizi5 = &guizi4; //四级指向三级
// 有完没完。。。
指针和数组的纠缠
1. 指针表示法和数组表示法
数组完全可以使用指针来访问, days[3] 和 *(days+3) 等同
2. 存储指针的数组[指针数组]
定义: 类型 *指针数组名[元素个数] ;
int *qishou[2]; //定义一个有两个元素的指针数组,每个元素都是一个指针变量
指针和二维数组
1. 指向数组的指针【数组指针】
int (*p)[3]; //定义一个指向三个成员的数组的指针
访问元素的两种方式:数组法:(*p)[j]、指针法:*((*p)+j)
2. 使用普通指针访问二维数组
A[0]
的含义:A
是一个二维数组,A[0]
表示它的第 0 行(即 A[0][0]
、A[0][1]
、A[0][2]
)。在 C/C++ 中,A[0]
会被隐式转换为 &A[0][0]
,即第 0 行的首地址(也就是 A[0][0]
的地址)。&A[0][0]
的含义:直接取 A[0][0]
的地址,显然也是指向二维数组的第一个元素。
数组与指针的区别
数组:数组是用于储存多个相同类型数据的集合。
指针:指针是一个变量,但是它和普通变量不一样,它存放的是其它变量在内存中的地址。
1. 赋值
数组:只能一个一个元素的赋值或拷贝
指针:指针变量可以相互赋值
2. 表示范围
数组有效范围就是其空间的范围,数组名使用下表引用元素,不能指向别的数组
指针可以指向任何地址,但是不能随意访问,必须依附在变量有效范围之内
3. sizeof
数组:
数组所占存储空间的内存:sizeof(数组名)
数组的大小:sizeof(数组名)/sizeof(数据类型)
指针:
在 32 位平台下,无论指针的类型是什么,sizeof(指针名)都是 4.
在 64 位平台下,无论指针的类型是什么,sizeof(指针名)都是 8.
4. 指针数组和数组指针
针指数组:
int *qishou[2];//定义一个有两个元素的指针数组,每个元素都是一个指针变量
int girl1= 167;
int girl2 = 171;
qishou[0] = &girl1;
qishou[1] = &girl2;
数组指针:
int (*p)[3]; //定义一个指向三个成员的数组的指针
访问元素的两种方式:
int A[4][3]={{173, 158, 166},
{168, 155, 171},
{163, 164, 165},
{163, 164, 172}};
p = &A[0];
数组法: (*p)[j]
指针法: *((*p)+j)
5. 传参
数组传参时,会退化为指针!
(1)退化的意义:C 语言只会以值拷贝的方式传递参数,参数传递时,如果只拷贝整个数
组,效率会大大降低,并且在参数位于栈上,太大的数组拷贝将会导致栈溢出。
(2)因此,C 语言将数组的传参进行了退化。将整个数组拷贝一份传入函数时,将数组名
看做常量指针,传数组首元素的地址。
#include <stdio.h>
#include <stdlib.h>
/*------------------ <一维数组传参> -----------------------*/
/*方式一: 形参不指定数组大小
用数组的形式传递参数,不需要指定参数的大小,
因为在一维数组传参时,形参不会真实的创建数组,
传的只是数组首元素的地址。
*/
void method_1(int arr[], int len)
{
for(int i=0; i<len; i++){
printf(" arr[%d] = %d\n", i, arr[i]);
}
}
//方式二:指定数组大小
void method_2(int arr[10])
{
for(int i=0; i<10; i++){
printf(" arr[%d] = %d\n", i, arr[i]);
}
}
//方式三: 一维数组传参退化,用指针进行接收,传的是数组首元素的地址
void method_3(int *arr, int len){
for(int i=0; i<len; i++){
printf(" arr[%d] = %d\n", i, arr[i]);
}
}
int main(){
int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
method_1(arr, 10);
printf("------华丽的分隔线------\n");
method_2(arr);
printf("------华丽的分隔线------\n");
method_3(arr, 10);
system("pause");
return 0;
}
#include <stdio.h>
#include <stdlib.h>
/*-------------------- <指针数组传参> -----------------------*/
//方式一: 指针数组传参,声明成指针数组,不指定数组大小
void method_4(int *arr[], int len){
for(int i=0; i<len; i++){
printf(" arr[%d] = %d\n", i, *arr[i]);
}
}
//方式二: 指针数组传参,声明成指针数组,指定数组大小
void method_5(int *arr[10]){
for(int i=0; i<10; i++){
printf(" arr[%d] = %d\n", i, *arr[i]);
}
}
//方式三: 二维指针传参
//传过去是指针数组的数组名,代表首元素地址,而数组的首元素又是一个指针,
//就表示二级指针,用二级指针接收
void method_6(int **arr, int len){
for(int i=0; i<len; i++){
printf(" arr[%d] = %d\n", i, *(*(arr+i)));
}
}
int main(){
int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *arr_p[10] = {0};
for(int i=0; i<10; i++){
arr_p[i] = &arr[i];
}
method_4(arr_p, 10);
printf("------华丽的分隔线------\n");
method_5(arr_p);
printf("------华丽的分隔线------\n");
method_6(arr_p, 10);
system("pause");
return 0;
}
void 类型指针
void => 空类型 void* => 空类型指针,只存储地址的值,丢失类型,无法访问,要访问其值,我们必须对这个指针做出正确的类型转换,然后再间接引用指针。所有其它类型的指针都可以隐式自动转换成 void 类型指针,反之需要强制转换。
#include <stdio.h>
#include <stdlib.h>
int main(void){
int arr[]={1, 2, 3, 4, 5};
char ch = 'a';
void *p = arr;//定义了一个void 类型的指针
//p++; //不可以,void * 指针不允许进行算术运算
p = &ch; //其它类型可以自动转换成void * 指针
//printf("数组第一个元素: %d\n", *p); //不可以进行访问
printf("p: 0x%p ch: 0x%p\n", p, &ch);
char * p1 = (char *)p; //强制类型转化
printf("p1 指向的字符是: %c\n", *p1);
system("pause");
return 0;
}
函数指针
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int compare_int(const void *a, const void *b){
//printf("调用compare_int 啦,你好骚气哦! \n");
int *a1 = (int *) a;
int *b1 = (int *) b;
//printf("a 的地址: 0x%p b的地址: 0x%p\n", &a, &b);
return *b1 - *a1;
}
int compare_char(const void *a, const void *b){
//printf("调用compare_char 啦,你好骚气哦! \n");
char c1 = *((char *) a);
char c2 = *((char *) b);
if(c1>='A' && c1<='Z') c1+=32;
if(c2>='A' && c2<='Z') c1+=32;
return c1 - c2;
}
int main(void){
int x = 10;
int y = 20;
//函数有没有地址?
//printf("compare_int 的地址: 0x%p \n", &compare_int);
//compare_int(&x, &y);
//函数指针的定义 把函数声明移过来,把函数名改成 (* 函数指针名)
int (*fp)(const void *, const void *);
/*贝尔实验室的C和UNIX的开发者采用第1种形式,而伯克利的UNIX推广者却采用第2
种形式ANSI C 兼容了两种方式*/
fp = &compare_int; //
(*fp)(&x, &y); //第1种,按普通指针解引的放式进行调用,(*fp) 等同于compare_int
fp(&x, &y); //第2种 直接调用
//qsort 对整形数组排序
int arr[]={2, 10, 30, 1, 11, 8, 7, 111, 520};
qsort(arr, sizeof(arr)/sizeof(int), sizeof(int), &compare_int);
for(int i=0; i<sizeof(arr)/sizeof(int); i++){
printf(" %d", arr[i]);
}
//qsort 可以对任何类型的数组进行排序
char arr1[]={"abcdefghiABCDEFGHI"};
qsort(arr1, sizeof(arr1)/sizeof(char)-1, sizeof(char), &compare_char);
for(int i=0; i<sizeof(arr1)/sizeof(char)-1; i++){
printf(" %c", arr1[i]);
}
system("pause");
return 0;
}