指针
内存编号<=>内存地址<=>地址<=>指针
内存单元的地址称为指针,专门用来存放地址的变量,指针指针变量;在不影响,有时对地址、指针和指针变量不区分
-int *p
-int :指针所指向的类型
-int * :指针变量的类型
-p :指针变量的名字
-p :变量指针 内容是地址
-*p :指针所指对象,内容是数据
-&p :指针变量占用的存储区域的地址,是个常量
指针运算
-指针运算以指针变量所存在的地址量作为运算量而进行的运算
-实质是地址的运算
-只能进行赋值运算、算术运算和关系运算
算术运算:
px+n:指针向地址大的方向移动n个数据,(px)+sizeof(px的类型)*n
px-n:…小的方向移动n个数据
px++、++px:…大的方向移动1个数据
px–、–px:…小的方向移动1个数据
px-py:两指针之间相隔数据元素的个数,数据类型不同不能相减
指针与数组
一维数组的数组名位一维数组的指针(起始地址)指针常量
-x[i]、*p(x+i)、*(x+i)和px[i]具有完全相同的功能:访问数组第i个元素
#int include <stdio.h>
int main(void)
{
int buf[]={3,3,4,5,6};
int *p=buf;
printf("buf[2] = %d\n",buf[2]);
printf("buf[2] = %d\n",*(buf+2));
printf("buf[2] = %d\n",p[2]);
printf("buf[2] = %d\n",*(p+2));
return 0;
}
-指针变量和数组在访问数组中元素时,一定条件下有相同形式,因为指针变量和数组名都是地址量
-二者本质上是不同的,指针变量时地址变量,而数组的指针时地址常量
#int include <stdio.h>
int main(void)
{
int buf[]={3,3,4,5,6};
int *p=buf;
printf("&buf+1=%p\n",&buf+1); // 取buf地址加一相当于加了整个数组的长度,也就是地址+20
return 0;
}
指针与二维数组
二维数组连续存储,按行优先存储
// 使用一级指针遍历二维数组
#include <stdio.h>
int main(void)
{
int buf[2][3]={{1,2,3},{4,5,6}};
int *p=(int *)buf;
for(int i=0;i<6;i++)
{
printf("%d\n",*p++);
}
return 0;
}
行指针
-存储类型 数据类型 (*指针变量名)[表达式] int a[2][3] int (*p)[3]
-int *p[3]是一个可以存放三个int *整型地址元素的数组
#include <stdio.h>
int main(void)
{
int buf[2][3]={{1,2,3},{4,5,6}};
int (*p)[3]=buf;
// 以下都是指buf[1][1]=5
printf("buf[1][1]=%d\n",buf[1][1]);
printf("buf[1][1]=%d\n",(*(p+1))[1]);
printf("buf[1][1]=%d\n",*(p[1]+1));
printf("buf[1][1]=%d\n",p[1][1]);
printf("%d",*(*(p+1)+1));
printf("%d",(int *)p[1][1]);
return 0;
}
字符数组
char数据类型的指针边变量称为字符型指针变量
-char str[]="Hello"; char *p=str;
-char *str="hello";
// 初始化字符指针是把字符串的首地址赋予指针,并不是把字符串复制到指针中。前者存在于内存数据区,在栈里;二后者在只读取,无法修改或处理
#include <stdio.h>
#include <string.h>
// 指针实现字符串连接
int main(void)
{
char buf1[20]="hello";
char buf2[]=" world";
char *p=buf1;
char *q=buf2;
while(*p)
p++;
while(*p++=*q++)
NULL;
printf("%s\n",buf1);
return 0;
}
指针数组
具有相同存储类型和数据类型的指针变量构成的集合,存储类型 数据类型 *指针数组名 [];指针数组名表示该指针数组的起始地址,int *p[3]注意和数组指针(行指针)的不同
// 利用指针处理二维数组,计算出所有元素的和
#include <stdio.h>
#include <string.h>
// 利用指针处理二维数组,计算出所有元素的和
int main()
{
int a[2][3]={{1,9,4},{9,1,6}};
int *p[2];
int i,j,sum;
p[0]=&a[0][0];
p[1]=&a[1][0];
for(i=0;i<2;i++)
{
for(j=0;j<3;j++)
{
printf("%d %d ",a[i][j],*(p[i]+j));
sum+=(*(p[i]+j));
}
}
printf("sum=%d\n",sum);
printf("%d\n",sizeof(p));
return 0;
}
多级指针
-把一个指向指针变量的指针变量称为多级指针
-对于指向处理数据的指针变量称为一级指针变量,一级指针
-指向一级指针的指针变量称为二级指针变量,二级指针
-二级指针表述:存储类型 数据类型 **指针名
多级指针的运算
指针变量加1,向地址大的方向移动一个目标数据
int main(void)
{
char *ps[3]={"aaa","bbb","ccc"};
char **pps=ps;
printf("ps=%s\n",ps[0]);
printf("pps=%s\n",*(pps+1)); // 取第一个元素aaa
printf("%c\n",**pps); // 取第一个字符串元素的第一个字符
return 0;
}
void指针
void指针是一种不确定数据类型额指针变量,可以通过强制类型转换让该变量指针指向任何数据类型的变量
-void *指针变量名称
-对于void指针,在没有强制类型转换之前,不能进行任何指针的算术运算(不确定)
#include <stdio.h>
#include <string.h>
// 学习地址:https://www.bilibili.com/video/BV1bo4y1Z7xf?p=2&vd_source=0d3fa7725ce01ed5e63fd14745adb26b
void fun1(int x){
x = x + 1;
}
void fun2(int *y) {
*y = *y + 1;
}
int sumofelements(int a[], int size) {
int i, s = 0;
// int size = sizeof(a) / sizeof(a[0]);
// 在主函数外的a实际上与主函数内的a是不同的,虽然名字相同但是他们在stack的不同地方
// 也就是说主函数的a实际长度为20,而这里的a长度为4,类似于一个整型指针
// 编译器只是复制了主函数数组a的首地址给到主函数外的a
// 在这里编译器把int a[]隐式地处理成int a而不是数组
// 数组可以很大,所以不会拷贝整个数组到函数外,这样资源消耗太大
// 数组一般不用传值方式,而是传引用
for (i = 0; i < size; i++) {
s += a[i];
}
return s;
}
void print(char *C) {
int i = 0;
while (C[i] != '\0'){
printf("%c", *C);
C++; // 或者这里用 C++ 实现数组自增,因为在print函数里C相当于指针
}
printf("->end\n");
}
void phw() {
printf("hello world!\n");
}
int *Add(int* a, int* b) { // 不同于上面的Add,这里传的是a的地址
int c = (*a) + (*b);
return &c;
}
int add(int *a,int *b) {
printf("address of a in add = %d\n", &a); // 传值
printf("address of a in add deliver by pointer = %d\n", a); // 传地址
printf("value at address stored in a of add = %d\n", *a);
int c = (*a) + (*b);
return c;
}
void printhello(const char *name) {
printf("hello %s\n", name);
}
void A() {
printf("hello");
}
void B(void (*ptr)()) {
ptr();
}
int main()
{
/* // 指针指向问题
int a = 10;
int* p;
p = &a; // 此处将a的地址赋予p
printf("%d\n",*p);
*p = 20;
printf("%d\n", a);
int b=30;
*p = b; // 和p=&a不同,这里p没有链接到b的地址,所以改变p的值并不会改变b的值
*p = 40;
printf("%d\n", b);
*/
/* // 地址与指针
int a = 10;
int* p;
p = &a;
printf("address p is %d\n",p);
printf("value at address p is %d\n", *p);
printf("size of integer is %d bytes\n", sizeof(int));
printf("address p+1 is %d\n", p+1);
printf("value at address p+1 is %d\n", *(p+1));
printf("value of *p+1 is %d\n", *p+1);
*/
/*
// 指针类型转换,void指针——通用指针类型,指针算术
// 既然指针只存放地址,为什么需要不同类型的指针?因为这样就可以访问和修改这些地址对应的值
// 不同数据有不同大小,int有4bytes,char-1,float-1,用户自定义的类和结构体又有不同大小
int a = 1025;
int *p;
p = &a;
printf("size of integer is %d bytes\n", sizeof(int));
printf("address of p = %d, value of p = %d\n",p,*p);
printf("address of p+1 = %d, value of p+1 = %d\n", p + 1, *(p + 1));
char *p0;
p0 = (char *)p; // 直接p0 = p会因为类型不对应报错,所以需要类型转换
// 此处p0指向字符的指针,而p是指向整型的指针
printf("size of char is %d bytes\n",sizeof(char));
printf("address of p0 = %d, value of p0 = %d\n", p0, *p0);
// 这里*p0的值输出来1,是因为1025在二进制上是00000000 00000000 00000100 00000001
// 强制转换后最右边的1个字节的地址输入到char指针p0中也就是00000001
// 所以最后只剩下*p0 = 00000001 所以*p0 = 1
printf("address of p0+1 = %d, value of p0+1 = %d\n", p0 + 1, *(p0 + 1));
// 这里*(p0+1)也就是变成00000100,所以值变成4
printf("\n################################\n \n");
void *p1; // 定义一个通用型指针,不能直接对他解引*p0,只可以读取地址
p1 = p;
printf("address of p1 = %d\n",p1);
*/
/*
// 指向指针的指针
int x = 5;
int *p = &x;
*p = 6;
int **q = &p;
int ***r = &q;
printf("p = %d\n",*p);
printf("*q = %d\n", *q); // 存放p的地址
printf("**q = %d\n", *(*q)); // p的值
printf("**r = %d\n", **r); // 存放q的地址
printf("***r = %d\n", ***r); // x的值
***r = 10;
printf("x = %d\n",x);
**q = *p + 2;
printf("x = %d\n", x);
printf("r = %d\n", ***r);
*/
/*
// 指针为函数传值和传引用,上面有两个void函数fun1 fun2是这里下面
int a = 5, b = 6;
fun1(a);
printf("new a is = %d\n",a);
fun2(&b);
printf("new b is = %d\n", b);
*/
/*
// 指针与数组
int a[] = { 2,4,5,8,1 };
for (int i = 0; i < 5; i++) {
printf("address = %d\n", a+i); // 数组本身就像指针,有些操作相同
printf("address = %d\n", &a[i]);
printf("value = %d\n", a[i]);
printf("value = %d\n", *(a+i));
}
int* p = a;
p++; // 不能a++
printf("%d\n",*p);
*/
/*
// 数组作为函数参数
int a[] = { 1,2,3,4,5 };
int size = sizeof(a) / sizeof(a[0]);
int total = sumofelements(a, size);
printf("sun of the array = %d\n",total);
*/
/*
// 指针和字符数组
char c[] = "JOHN";
printf("size of c in bytes is : %d\n",sizeof(c));
int len = strlen(c);
printf("length of c is : %d\n", len); // 长度不回包含结束符\0
char b[] = { 'J','O','H','N','\0' }; // 这个时候结束符\0就不再是隐式的了
printf("size of b in bytes is : %d\n", sizeof(b));
int len1 = strlen(b);
printf("length of b is : %d\n", len1); // 长度不回包含结束符\0
char C[20] = "Hello";
print(C); // 上面定义一个能够输出C的函数print,用于当输出到\0时停止输出
// 在一些严格的编译器中,这行printf其实是将长度为20的整个数组都打印出来
// 在函数print中,并不知道数组长度,但是只要读到\0就会停止输出
*/
/*
// 指针和二维数组、多维数组
int c[3][2][2] = {
{{2,5},{7,9}},
{{3,4},{6,1}},
{{0,8},{11,13}}};
printf("%d %d %d %d\n", c, *c, c[0], &c[0][0]);
printf("%d",*(c[0][0]+1));
*/
/*
// 指针作为函数返回
//int x = 2, y = 4;
//int z = Add(x, y); // 函数Add,局部变量,无法访问main函数中的变量
//printf("sum = %d\n", z);
int a = 2, b = 4;
phw();
int* ptr = Add(&a, &b);
printf("ptr = %d\n", *ptr);
int c = add(&a, &b);
printf("address of a in main = %d\n", &a);
printf("sum = %d\n", c);
*/
/*
// 函数指针,存储函数的指针,下面代码课程是可以运行的
void (*ptr)(char*);
ptr = printhello;
ptr("tom");
*/
// 指针的使用案例,回调函数
// void (*p)() = A;
// B(p);
B(A); // 以上两行代码可以用这个表示
return 0;
}