C/C++-指针
- 参考
- 1. 指针
- 指针与内存/地址
- 指针使用
- 2.数组指针
- 数组/指针/sizeof
- 一维数组与指针
- 多维数组指针
- 3.字符/字符串指针
- 4. 其他指针
- 二级指针 -- 还没看
- 空指针
- void指针
- 野指针
- 5.指针与函数
- 函数指针
- 引用传递
- 指针函数
- 6.结构体指针
- 结构体数组指针
- ???
- c++ 对象指针
参考
https://www.bilibili.com/video/BV1s5411J78m?p=3&spm_id_from=pageDriver&vd_source=7155082256127a432d5ed516a6423e20 – 入门 讲的不错
1. 指针
适用于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此时变成了5
char 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 <stdio.h>
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); //000000000061FE14
int b=20;
*p=b; // 只是值的修改
printf("%d\n",a); // 20
printf("%p\n",p); // 000000000061FE14
printf("%p\n",&a); // 000000000061FE14 不会改变a的地址
printf("%p\n",&b); // 000000000061FE10
p=&b;
printf("%p\n",p); // 000000000061FE10,指向了b
printf("%p\n",&a); // 000000000061FE14
printf("%p\n",&b); // 000000000061FE10
return 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
指针的比较运算
对指针变量进行比较运算时,比较的是指针变量本身的值,也就是数据的地址,如果地址相等那么两个指针指向用一个数据,否则指向不同的数据
2.数组指针
数组指针指向的是数组中的一个具体元素,而不是整个数组,所以数组指针的类型和数组元素的类型有关
数组/指针/sizeof
数组在内存中只是数组元素的简单排列,没有开始和结束标志
不能使用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));
}
}
3.字符/字符串指针
指向字符串的指针
通常利用字符数组表示字符串
字符数组归根结底还是一个数组,所以数组大部分的功能都适用
char s[]="hello";
char* p=s;
for (int i=0;i<sizeof(s)-1;i++){
printf("%c,%c,%c\n",s[i],p[i],*(p+i));
}
for (int i=0;i<strlen(s);i++){
printf("%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
4. 其他指针
二级指针 – 还没看
指向指针的指针
指针存放的是一个变量的地址,而指针的地址也可以被另一个指针存放
指针变量也是变量,因此也需要存储空间
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指针
void*
表示一个有效指针,指向实实在在的数据,只是数据的类型尚未确定,在后序使用过程中需要进行强制类型转换
char* s=(char*)malloc(sizeof(char)*30);
野指针
如果一个指针指向的内存没有访问权限,或者指向一块已经释放掉的内存,那么就无法对该指针进行操作,这样的指针就是野指针
free
free§并不能改变指针p的值,p依然指向以前的内存,为了防止再次使用该内存,建议将p的值手动置为NULL
free§ 只是释放掉动态分配的内存,p的值并不是NULL,仍然指向之前这个被释放掉的内存,所以if§仍然会执行,但是输出p指向的内存会报错
避免野指针
初始化为NULL
free之后,赋值为NULL
5.指针与函数
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;
}
6.结构体指针
一个指针变量指向结构体,叫做结构体指针
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 <stdio.h>
#include <stdlib.h>
#define N 2
struct 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;i<N;i++,pa++){
scanf("%s %d",pa->name,&pa->num);
}
pa=boya; // 为什么要写两遍??
for (i=0;i<N;i++,pa++){
fprintf(fp,"%s %d\n",pa->name,pa->num);
}
return 0;
}
c++ 对象指针
见 c++ – 面向对象