https://www.bilibili.com/video/BV1s5411J78m?p=3&spm_id_from=pageDriver&vd_source=7155082256127a432d5ed516a6423e20 – 入门 讲的不错
适用于C/C++
数据以二进制的形式存在内存中,每个数据占n个字节
每个字节都有一个内存地址
指针:就把他理解为一个数据,只要是数据就以二进制的形式存储在内存中
指针(在内存中存的值是 被指向的 那个数据的 内存地址值) – 见下图
且每个指针在内存中也有一个地址
总结:指针就是被指向数据的内存地址
CPU访问内存时需要的是地址,而不是变量名和函数名,变量名和函数名只是地址的一种助记符,当源文件被编译和链接成可执行程序后,变量名和函数名都被替换成地址
编译和连接过程的一项重要的任务就是找到这些名称所对应的地址
通常:变量名表示的是数据本身;
函数名、字符串名、数组名是表示的是代码块和数据块的首地址
地址本质上是一个整数
一个变量A 存放的是 另一个变量B的指针,那么将A变量 称为 指针变量
指针必须进行初始化!
指针创建
dataType* name=value
,**dataType
表示该指针所指向的数据的类型**
注意指针的类型是dataType*
而不是dataType
int *p; // 表示指针p指向的内容是整型
int* p; //表示指针p是一个int*类型
int a;
int *p; // *表示指针变量
p=&a; //p的值 时a的地址值int *p=&a; // 等价
int* p=&a; // 等价 ; 感觉这种更符合实际的意思printf("%d",*p); // 取值
指针修改
int a;
int *p;
p=&a; //p的值 时a的地址值
// 等价于 int *p=&a;
// 等价于 int* p=&a; //感觉这种更符合实际的意思a=1;
&p // p的地址值
*p // *表示取消引用,*p 取了p的值(地址)所指向的数据
*p=10; // a此时变成了5char c;
char *pc;double d;
double *pd;// *p++ 等价于*(p++)
int a=10;
int* p=&a;
printf("%p\n",p);
printf("%d\n",*p++);
printf("%d\n",a);
printf("%p\n",p);
#include int main(){int a;a=10;int *p;p=&a;printf("%d\n",p); // 6422036 'python':hex(6422036) =='0x61fe14'printf("%p\n",p); //000000000061FE14 printf("%p\n",&a); //000000000061FE14int b=20;*p=b; // 只是值的修改printf("%d\n",a); // 20printf("%p\n",p); // 000000000061FE14printf("%p\n",&a); // 000000000061FE14 不会改变a的地址printf("%p\n",&b); // 000000000061FE10p=&b;printf("%p\n",p); // 000000000061FE10,指向了bprintf("%p\n",&a); // 000000000061FE14printf("%p\n",&b); // 000000000061FE10return 0;
}
指针算数
指针的值+1,指针的值+
dataType
个字节
尽量不要对变量的指针进行运算,这样没有意义
int a;
a=10;
int *p;
p=&a;
printf("%p\n",&a); // 000000000061FE14
printf("%p\n",p+1); // 000000000061FE18
printf("%p\n",&a); // 000000000061FE14
*
运算符优先级高于双目运算符
int a=1;
int* p=&a;
printf("%d\n",*p+1); // 2
指针的比较运算
对指针变量进行比较运算时,比较的是指针变量本身的值,也就是数据的地址,如果地址相等那么两个指针指向用一个数据,否则指向不同的数据
数组指针指向的是数组中的一个具体元素,而不是整个数组,所以数组指针的类型和数组元素的类型有关
数组在内存中只是数组元素的简单排列,没有开始和结束标志
不能使用int *p=arr; sizeof§/sizeof(int),因为p只是一个指向int类型的指针,编译器并不知道它指向的到底是一个整数还是一些列的整数(数组),所以sizeof§求得的是p这个指针变量本身所真用的字节数,而不是整个数组占用的字节数。
指针指向的数据类型可以不同
指针变量占用的内存空间相同
数组指针:指向数组的指针
不同于指针数组!!!
数组名可以认为是做一个指针,指向数组的第0个元素
数组本身是指针这个说法不准确!!!
第0个元素的地址称为数组的首地址
数组指针 指向的是数组中的一个具体元素,而不是整个数组,所以指针数组的类型和数组元素的类型有关
数组指针与数组名不同,数组名不可以改变,而数组指针可以改变
(下面的具有 右结合性)
*p++ 等价于*(P++)
,不能使用*arr++
,因为数组名不能改变
*++p 等价于*(++P) 等价于*(p+1)
(*p)++只对数值进行改变
type *p=arr
p+1相当于移动了一个type类型的字节
int a[3]={1,2,4};
int* p=a;
// 下面三个是等价的
printf("%p\n",p);
printf("%p\n",a);
printf("%p\n",&a[0]);// printf("",a++); // 这个是错误的,数组名是不能改变的
printf("%d\n",*p++); // 1
printf("%d\n",*(p++)); // 1,*p赋值之后,再++
printf("%d\n",*p+1); // 2
printf("%d\n",*(p+1)); // 2
printf("%d\n",*(++p)); //2
printf("%d\n",*++p); //2
int main()
{int a[3]={1,4,5};int *p=a;// printf("%d\n",*p++);// printf("%d\n",*p);printf("%d\n",(*p)++);printf("%d\n",*p);// printf("%d\n",*(p++));// printf("%d\n",*p);for (int i=0;i<3;i++){printf("%d\n",a[i]);}return 0;
}
重要
[]
符号具有取内容的作用
a[i],*(a+i),p[i], *(p+i) 这四个是等价的
&a[i],(a+i),&p[i], (p+i) 这四个是等价的
int a[3]={1,2,3};
for (int i=0;i<3;i++)
{printf("%d,%d,%d,%d\n",a[i],*(a+i),p[i],*(p+i));printf("%p,%p,%p,%p\n",&a[i],(a+i),&p[i],(p+i));
}
https://blog.csdn.net/angiusc/article/details/106599792
二维数组与一维数组的关系
a[2][3] 可以看成是2个一维数组,每个一维数组的数组名是a[0],a[1]
嵌套理解 a[3][4] 中 a[0]是一个数组(也是一个一维数组名)
// 可以定义1xX的数组
int a[1][3]={1,2,3};
for (int i=0;i<1;i++){// for (int j=0;j<3;j++){// printf("%d\n",a[i][j]);// }printf("%p\n",a[i]);printf("%p\n",&a[i][0]); // &a[i][0] 与a[i] 等价printf("%d\n",a[i][0]);
}
a[2][3] 可以看成是2个一维数组,每个一维数组的数组名是a[0],a[1]
对于一维数组及其指针
type *p=arr
p+1相当于移动了一个type类型的字节
对于二维数组及其指针,p+1应该移动整个一维数组对应的字节,type (*p)[n]
,(*p) 表示指针,int [n] 表示指针指向的数据类型
int a[1][3]={1,2,3};
for (int i=0;i<1;i++){// for (int j=0;j<3;j++){// printf("%d\n",a[i][j]);// }printf("%p\n",a[i]);printf("%p\n",&a[i][0]); // &a[i][0] 与a[i] 等价printf("%d\n",a[i][0]); // 这是数值
}int a[2][3]={{1,2,3},{4,5,6}};
// 下面三个是等价的
// *a[2][3] 可以看成是2个一维数组,每个一维数组的数组名是a[0],a[1]*
printf("%p\n",a);
printf("%p\n",a[0]);
printf("%p\n",&a[0][0]);// 下面是等价的
int (*p)[3]=a;
printf("%p\n",p+1);
printf("%p\n",a+1);
printf("%p\n",a[1]);
printf("%p\n",&a[1][0]);
重要
a[i]==*(a+i)==p[i]==*(p+i)
a[i][j]==*(a[i]+j)==*(*(a+i)+j)==p[i][j]==*(p[i]+j)==*(*(p+i)+j)
int (*p)[3]=a;
for (int i=0;i<2;i++)
{for (int j=0;j<3;j++){printf("%d,%d,%d,%d,%d,%d\n",a[i][j],*(a[i]+j),*(*(a+i)+j),p[i][j],*(p[i]+j),*(*(p+i)+j));}
}/* /// 补充 ///// 理解对于1维数组int a={1,2,3};int* p=a;a[1]==*(P+1)==*(a+1)==p[1]&a[1]==p+1==a+1// 二维数组// 看成嵌套的一维数组int a[][2]={1,2,3,4};int (*p)[2]=a; *(*(p+1)+1) ==a[1][1]p指向了一个都是指针的数组(首地址),而数组元素的每个指针,指向了一个一维数组(首地址)p+1==a[1]==&a[1][0]*(p+1)==a[1]==&a[1][0] // 这个记忆方法放到下面可以理解// 下面三个方法是等价的printf("%d\n",*(a[1]+1)); //a[1]也是一个指针,表示一维数组printf("%d\n",*(*(p+1)+1)); // *(p+1) p+1表示一个一维数组,printf("%d\n",a[1][1]);
*/
重要 - 第二种表达方法
int a[2][3]={{1,2,3},{4,5,6}};int *p=&a[0][0];
for (int i=0;i<2;i++)
{for (int j=0;j<3;j++){printf("%d,%d\n",a[i][j],*((p+i*3)+j));}
}
指向字符串的指针
通常利用字符数组表示字符串
字符数组归根结底还是一个数组,所以数组大部分的功能都适用
char s[]="hello";
char* p=s;
for (int i=0;iprintf("%c,%c,%c\n",s[i],p[i],*(p+i));
}for (int i=0;iprintf("%c,%c,%c\n",s[i],p[i],*(p+i));
}
另一种字符串表示方法
直接使用一个指针指向字符串
被称为:字符常量
能够修改指针指向的地址,但是不能修改内容
char *s="hello";char *s;
s="hello";s="world"; // 正确
// s[2]='1';// 错误
两种创建方式的不同
char s[]="xxx"
,以字符数组存储在全局数据区或栈区,具有读写权限
char* s="xxx"
,存储在常量区,只有读取权限,一旦定义不能修改
两种字符串的补充
// 初始化注意事项
// char s1[10];
// s1="hello"; // 这种不行
char *s2;
s2="world"; // 这种可以// 访问
char arr1[]="hello world";
char *arr2="hello cpp";for (int i=0;i<11;i++)
{printf("%c,%c",arr1[i],*(arr1+i));
}
printf("\n");
for (int i=0;i<9;i++)
{printf("%c,%c\n",arr2[i],*(arr2+i));
}/* 重要 */
printf("%s\n",arr1); // hello world
printf("%s\n",arr2); // hello cpp
printf("%s\n",arr1+1); // ello world
printf("%s\n",arr2+2); // llo cpp
*
运算符优先级高于双目运算符
char a='c';
char* p=&a;
printf("%d\n",a); //99
printf("%c\n",*p+1); //d
printf("%d\n",*p+1); //100
指向指针的指针
指针存放的是一个变量的地址,而指针的地址也可以被另一个指针存放
指针变量也是变量,因此也需要存储空间
int a=100;
int* p1=&a;
int** p2=&p1; // 可以理解为 (int*)* p2=&p1 因为指向的是指针,而指针的类型是int*,而创建的又是一个指针,所以是(int*)*
对未初始化的指针赋值为NULL
空指针是不指向任何数据的指针,是无效指针
// NULL 其实是一个宏定义,指向了内存的0地址,
#define NULL ((Void*)0)
char* s=NULL;if (p==NULL){// ...
}
void*
表示一个有效指针,指向实实在在的数据,只是数据的类型尚未确定,在后序使用过程中需要进行强制类型转换
char* s=(char*)malloc(sizeof(char)*30);
如果一个指针指向的内存没有访问权限,或者指向一块已经释放掉的内存,那么就无法对该指针进行操作,这样的指针就是野指针
free
free§并不能改变指针p的值,p依然指向以前的内存,为了防止再次使用该内存,建议将p的值手动置为NULL
free§ 只是释放掉动态分配的内存,p的值并不是NULL,仍然指向之前这个被释放掉的内存,所以if§仍然会执行,但是输出p指向的内存会报错
避免野指针
初始化为NULL
free之后,赋值为NULL
https://blog.csdn.net/L_fengzifei/article/details/126291514
使指针变量指向函数坐在的内存区域,然后通过指针变量就可以找到并调用该函数
returnType (*p)(param list)
,returnType
为函数返回值类型
typedef int (*PTR_TO_FUNC)(int, int);
PTR_TO_FUNC pfunc;// 例子
typedef int (*PTR_TO_FUNC)(int, int);
int max(int a, int b){return a>b ? a : b;
}
PTR_TO_FUNC pfunc = max;
printf("max: %d\n", (*pfunc)(10, 20));
注意与 引用传递、指针函数的区别
int max(int a,int b){return a>b?a:b;
}int main(){int x=1;int y=2;int maxval;int (*pmax)(int,int)=max;maxval=(*pmax)(x,y); printf("%d\n",maxval);return 0;
}
下面是指针函数,返回值是指针,指针指向的数据类型
char *strlong(char* s1,char* s2){if (strlen(s1)>=strlen(s2)){return s1;}else{return s2; }
}
注意局部变量作为指针函数的返回值情况
函数运行结束后会销毁在他内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针请尽量不要指向这些数据,C语言没有任何机制来保证这些数据会一直有效,它们会在后续使用过程中可能引发运行时错误
// 下面的例子,有的编译器直接报错,提示 返回局部变量的指针
int *func()
{int n=100;return &n;
}int main()
{int *p=func(),n;n=*p;printf("%d\n",n);return 0;
}
一个指针变量指向结构体,叫做结构体指针
struct struct_name* var
struct str{char* name;int num;
} stu1={"tom",1};struct stu* pstu=&stu1;// 直接创建指针
struct str{char* name;int num;
} stu1={"tom",1},*pstu=&stu1;// 使用指针获取结构体成员
(*pstu).name; // 必须加括号
pstu->name; // 直接通过结构体指针获取成员,这种方法更有效
结构体指针作为函数参数传递
‘C-语言函数中的例子’
因为是数组,所以数组名可以作为元素的首地址
struct stu class[]={{"li",2},{"wang",3}
};// 下面两个是等价的,都表示地址
printf("%p\n",class);
printf("%p\n",&class[0]);// 所以可以利用指针指向地址
struct stu* pstu=class; // 下面这两种访问方法都是等价的
struct stu* pstu=class;
for (int i=0;i<2;i++){printf("%s,%d\n",(pstu+i)->num,(pstu+i)->age);
}for (int i=0;i<2;i++,pstu++){printf("%s,%d\n",pstu->num,pstu->age);
}
#include
#include #define N 2struct stu{char name[10];int num;
}boya[N],boyb[N],*pa,*pb;int main(){FILE* fp;int i;if ((fp=fopen("ex4.txt","wt+"))==NULL){puts("fail to open file");exit(0);}printf("input data\n");pa=boya;for (i=0;iscanf("%s %d",pa->name,&pa->num);}pa=boya; // 为什么要写两遍??for (i=0;ifprintf(fp,"%s %d\n",pa->name,pa->num);}return 0;
}
见 c++ – 面向对象