知识点一:内存地址的概述
系统会给变量、函数、数组、字符串等开辟空间,开辟空间也会给予一个起始地址(内存地址)
指针可以指向这些起始地址,也就是一个标记,这个标记就是指针
![内存地址](https://img-blog.csdnimg.cn/img_convert/6d1e69db51592527a9a25553ebcb31e3.png)
知识点二:指针变量
指针变量:本质就是一个变量
,也有指针变量的起始地址,在指针变量的内存中存放指向的地址,二级指针就可以保存一级指针,依次嵌套
在32位平台,指针变量任何类型
的地址编号都是 4 字节
![指针变量](https://img-blog.csdnimg.cn/img_convert/c023f3c5b305a12ad31b9110847dfa94.png)
知识点三:定义指针变量
*
:在定义一个指针变量时,起到标识作用,标识定义是一个指针变量除此之外,其他地方都表示获取一个指针变量保存的地址里面的内容
&
:取一个对应变量的地址,若对指针变量取地址,则是指针变量的地址(而不是指针变量指向的地址)
形式:类型 *指针变量名
知识点四:指针变量的使用
通过过 p 对所保存的地址空间进行读写操作
![指针变量定义](https://img-blog.csdnimg.cn/img_convert/8cc886d41b8a362a7d2d363892a25b8f.png)
void test01 ()
{
//num拥有一一个合法的空间
int num = 10;
//需求:请定义一个指针变量保存num的地址
//p就是指针变量变量名为p不是*p
//在定义的时候:*修饰p表示p为指针变量
int *p;
p = # //&num代表num变量起始地址(首地址)
//指针变量指向变量p的起始地址
printf("&num = %p\n",&num);
printf("p = %p\n",p);
//*p 等价num
printf("*p = %d\n",*p); //10==num
//*p = 100 == num
*p = 100;
printf("num = %d\n",num); //num = 100
scanf("%d",p); //如果此处为&p表示键盘给p赋值而不是给num赋值
printf("num = %d\n",num);
}
知识点五:指针变量取值 宽度
输出时是宽度里面的内容
![指针变量取值宽度](https://img-blog.csdnimg.cn/img_convert/89419df6341fc8f2e6c2ec8d49924ccb.png)
#include<stdio.h>
void test01 ()
{
int num = 0x01020304;
int *p;
short *p1;
char *p2;
p = #
p1 = #
p2 = #
printf("*p = %#x\n",*p); //*p = 0x1020304
printf("*p1 = %#x\n",*p1); //*p1 = 0x304
printf("*p2 = %#x\n",*p2); //*p2 = 0x4
}
int main(int argc,char *argv[])
{
test01();
}
知识点六:指针变量的 跨度
输出时是不输出跨度里面的内容,而是输出除了跨度内剩下且在宽度范围内的内容
![指针变量跨度](https://img-blog.csdnimg.cn/img_convert/559cbaab322cd3696e9d2eaacc8c3f89.png)
#include<stdio.h>
void test01 ()
{
int num = 0x01020304;
int *p;
short *p2;
char *p1;
p2 = #
p = #
p1 = #
printf("*p = %u\n",p); //*p = 6356608
printf("*p+1 = %u\n",p+1); //*p+1 = 635612
printf("*p1 = %u\n",p1); //*p1 = 6356608
printf("*p1+1 = %u\n",p1+1); //*p1+1 = 6356609
printf("*p2 = %u\n",p2); //*p2 = 6356608
printf("*p2+1 = %u\n",p2+1); //*p2+1 = 6356610
}
int main(int argc,char *argv[])
{
test01();
}
知识点七:指针和普通变量强制类型转换
形式:(需要强转的类型 *)变量名
普通变量:(需要强转的类型)变量名
强转后则变成强转的类型
知识点八:指针变量初始化
void test01 ()
{
int num = 10;
int data = 100;
//如果 局部指针变量 不初始化保存的是随机的地址编号(千万别取值)
int *p;
//不想让指针变量指向任何地方 应该初始化为NULL(千万别取值)
//#define NULL ((void *)0)
int *pl = NULL;
//将指针变量初始化为合法的地址(可以取值)
//*修饰p2为指针变量,p2=#
int *p2 = # //第一步: int *p2; 第二步: p2=#
printf("%d\n",*p2); //num==10
//指针变量p2本质是一个变量 可以更改指向
p2 = &data;
printf("%d\n",*p2); //data==200
}
总结:
1)指针变量初始化为NULL
int *p = NULL; //不要对p进行*p操作 容易出段错误
2)指针变量初始化为 合法空间
int num = 10;
int *p = # //第一步定义指针变量int *p 第二步给指针变量赋值: p=&num
知识点九:&取地址符 和 *指针解引用符区别(使用中…)
void test01 ()
{
int num = 10;
int *p;
//num的类型是int 类型
//&num的类型是int *类型
//如果对一个变量取地址 整个表达式的类型是 变量 的类型 +*
p=#
//p的类型是 int * 类型
//*p的类型是 int 类型
//如果对指针变量取 * 整个表达式的类型是 指针变量 的类型 -*
//高级总结:如果 & 和 * 同时存在可以相互抵消(从右往左)
//论证: *p ==num
//*p=*&num==num;
}
知识点十:指针的注意事项
1、void 不能定义变量
void num; //错误的 系统不知道num的大小
2、void * 可以定义变量
void *p; //p的类型为 void * 而void *指针类型32为平台4字节,系统知道给p开辟4字节
//p叫万能指针,p可以保存任意类型的一级指针
对于 p 不能直接使用 *p 操作,必须实现对 p 进行强制类型转换
void test01 ()
{
int num = 10;
void *p;
p = #
//printf("%d\n",*p); //错误:因为p的指向类型为void 系统确定不了宽度
printf("%d\n",*(int *)p); //p临时的指向类型为int 系统确定宽度4B
}
3、不要对没有初始化的指针变量 取*
int *p;
printf("*p = %d\n", *p); //不能这样取*输出
//因为p没有初始化 内容随机 也就足p指向了一个未知空间系统不允许用户 取值*p操作
4、不要对初始化为 NULL 的指针变量 取*
//NULL就是(void *) 0 地址,也是内存的起始地址受系统保护
int *p = NULL;
printf("*p = %d\n",*p); //也不能 *p
5、不要给指针变量赋普通的数值
int *p = 1000; // 此时的1000对于p来说是地址编号1000
//*p表示在地址编号为1000的位置 取值,而地址编号1000不是合法的空间所以不能*p
printf("*p = %d\n",*p); //也不能*p
6、指针变量不要操作越界的空间
char num=10;
int *p = #
//num只占1B空间,而p的指向类型为int 所以*p取值宽度为4B ,所以越界3B
printf("*p =%d\n", *p); //操作非法空间
知识点十一:数组元素的指针
普通变量保存是在一个空间里,所以跨度之后是输出除了跨度之外在宽度之内的内容
数组是里面分许多的小空间保存不同的内容,所以跨度之后是输出对应小空间里面的内容
通过数组元素的指针变量 遍历 数组的元素
![数组元素指针](https://img-blog.csdnimg.cn/img_convert/7bdff44b3c5613183cee4a236a160408.png)
void test1()
{
int i = 0;
int num[4] = {10,20,30,40};
int *p = &num[0]; //或int *p = num;
for(i = 0;i<4;i++)
{
printf("num = %d\n",*(p + i));
}
}
通过数组元素的指针变量给数组的元素获取键盘输入
void test2()
{
int i = 0;
int num[5] = {0};
int n = sizeof(num)/sizeof(num[0]);
int *p = &num[0]; //或int *p = num;
for(i = 0;i<n;i++)
{
//scanf("%d",&num[i]);
scanf("%d",p + i); //p + i == &num[i];
}
}
知识点十二:数组的[]和*()的关系
数组名arr 作为类型,代表的是数组的总大小sizeof (arr)
数组名arr 作为地址,代表的是首元素地址(第0个元素的地址)
&arr[0] == &*(arr+0) == arr+0 == arr
在使用中本质:[] 是 *( ) 缩写
缩写规则:+ 左边的值放在[]左边+右边的值放在[]里面
arr [1] *(arr + 1)
知识点十三:数组中 arr 和 &arr的区别
arr:数组的首元素地址
&arr:数组的首地址
数组名arr是一个符号常量,不能被赋值
![区别](https://img-blog.csdnimg.cn/img_convert/6d5468c7a0800c4a0149deb07f522173.png)
知识点十四:指向同一数组的两个元素的指针变量间关系
void testll()
{
int arr[5]={10, 20, 30, 40,50} ;
int*p1 = arr;
int *p2 = arr+3;
//1、指向同一数组的两个指针变量相减,返回的是相差元素的个数
printf("%d\n",p2- p1); //3
//2、指向同一数组的两个指针变量可以比较大小
if (p2>p1)
{
printf(">\n");
}
else
{
printf("<\n");
}
//3、 指向同一数组的两个指针变量 可以赋值
p1=p2; //p1 和 p2指向同一处地址
//4、指问同一数组的两个指针变量尽量不要相加
printf("p2=%u\n",p2);
p1+p2; //错误:越界很厉害了
//5、[]里面在不越界的情况下 可以为负数
printf("%d\n",p2[-2]); //20
}
知识点十五:指针数组
指针数组:本质是数组,只是数组的每个元素是指针
形式:类型 * 数组名 [元素个数]
![指针数组分析1](https://img-blog.csdnimg.cn/img_convert/bc649f51f5149f9660124a9a1fba16db.png)
![指针数组分析2](https://img-blog.csdnimg.cn/img_convert/08c8280b32d9e142f4ae606e7ac3c2bc.png)
#include <stdio.h>
void test1()
{
int num1 = 10;
int num2 = 20;
int num3 = 30;
//指针数组
int *arr[3]={&num1,&num2,&num3};
char *arr2[3] ;
//arr[0] = &num1, arr[1]=&num2, arr[2]=&num3
printf("%d\n",*arr[1]); //*arr[1]= * &num2=num2
printf("%d\n",sizeof(arr)); //12
printf("%d\n",sizeof(arr2)); //12
return;
}
int main(int argc,char *argv[])
{
test1();
return 0;
}
知识点十六:数组指针
形式:指向的数组的类型(*指针变量名)[指向的数组的元素个数]
本质是:指针变量,只是保存的是数组的首地址
![数组指针](https://img-blog.csdnimg.cn/img_convert/009744b81692d6c2aafeac066253ae7b.png)
void test03( )
{
int arr[5]={10,20,30, 40};
int (*p)[5]; //数组指针: 本质是一个指针变量只是该变量保存的是数组的首地址
printf("%d\n",sizeof(p)); //4
printf("p= %u\n",p); //p = 3612672
printf("p+1 = %u\n",p+1); //p+2 = 3612692
p = &arr; //&arr 才代表数组的首地址
printf("%d\n",*(*p+3)); //40
//*(*p+3) == *(*(p+0)+3)==*(p[0]+3)==p[0][3]
printf("%d\n",p[0][3]); //40
}
知识点十七:二维数组分析
![二维数组分析](https://img-blog.csdnimg.cn/img_convert/c31691dc10578110f049ea7f54875d76.png)
知识点十八:数组指针与二维数组的关系
![数组指针与二维数组的关系](https://img-blog.csdnimg.cn/img_convert/1503ad113ae2d376bb98dd379ec800ea.png)
void test03( )
{
int arr[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int i,j = 0;
int (*p)[4] = arr;
for(i = 0;i<3;i++)
{
for(j = 0;j<4;j++)
{
//printf("%d\n",*(*(p+i)+j));
printf("%d ",arr[i][j]); //1 2 3 4 5 6 7 8 9 10 11 12
}
printf("\n");
}
}
知识点十九:任何维度的数组在物理存储上都是一维
![数组分析](https://img-blog.csdnimg.cn/img_convert/b90dad26e570a0b2480864caf670e983.png)
知识点二十:多级指针
指针变量存放的是地址,但指针变量本身还有对应的地址,如果对指针变量取地址,就是指针变量本身对应的地址
![多级指针](https://img-blog.csdnimg.cn/img_convert/3cf9b1d6a8b4b31191c7e95df640cc29.png)
int *p;
int a = 10;
p = &a;
//*p:取p指向地址的值
//p:为p指向的地址
//&p:原本指针变量
知识点二十一: 指针变量作为函数的参数
如果想在函数内部 修改 外部变量的值,需要将外部变量的 地址 传递给 函数(以指针变量作为函数的参数)
由于p1和p2分别保存了data1和data2的地址,可以通过p1和p2间接的修改data1和data2的值
![指针变量作为函数参数](https://img-blog.csdnimg.cn/img_convert/821be2f1f947a7c09fa6459aa0f511ba.png)
#include <stdio.h>
int my_swap2(int *a,int *b) //a = &data1 b = &data2
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
void test03()
{
int data1 = 10,data2 = 20;
printf("data1 = %d,data2 = %d\n",data1,data2);
//函数内部修改外部变量的值需传递外部变量的地址
my_swap2(&data1,&data2);
printf("data1 = %d,data2 = %d\n",data1,data2);
}
int main(int argc,char *argv[])
{
test03();
return 0;
}
//如果交换的是 *变量,所对应的赋值的地址对应的值都会随着改变
//如果是指针变量相互赋值的话,只是地址转换,只能影响交换地址之间的值
int main(void)
{
int a = 100;
int b = 200;
int *a1;
int *b1;
int *a2;
int *b2;
a1 = &a;
b1 = &b;
a2 = a1;
b2 = b1;
//情况一
//a2 = b2;
//printf("a2 = %d,b2 = %d\n",*a2,*b2); //a2 = 200,b2 = 200
//printf("a1 = %d,b1 = %d\n",*a1,*b1); //a1 = 100,b1 = 200
//情况二,使用情况一需要屏蔽情况二
*a2 = *b2;
printf("a2 = %d,b2 = %d\n",*a2,*b2); //a2 = 200,b2 = 200
printf("a1 = %d,b1 = %d\n",*a1,*b1); //a1 = 200,b1 = 200
printf("a = %d,b = %d\n",a,b); //a = 200,b = 200
}
总结:函数内部修改外部变量的值请传外部变量的地址.
外部变量为0级指针,函数的形参为1级指针
外部变量为1级指针,函数的形参为2级指针
外部变量为2级指针,函数的形参为3级指针
外部变量为 n-1 级指针,函数的形参为 n 级指针
知识点二十二: 一维数组名作为函数的参数
1、如果函数内部想操作(读、写)外部数组的元素,请将外部数组的数组名传递函数
2、一维数组作为函数的形参会被优化成一级指针变量
#include <stdio.h>
//int my_input_array(int arr[5],int n)
//一维数组作为函数的形参会被优化成指针变量
int my_input_array(int *arr,int n)
{
printf("B:%d\n",sizeof(arr)); //4
int i = 0;
printf("请输入%d个int数据\n",n);
for(i = 0;i<n;i++)
{
scanf("%d",arr+i);
}
}
int my_print_array(int *arr,int n)
{
int i = 0;
for(i = 0;i<n;i++)
{
//printf("%d ",*(arr+i));
printf("%d ",arr[i]);
}
printf("\n");
}
void test03( )
{
int arr[5] = {0};
int n = sizeof(arr)/sizeof(arr[0]);
printf("A:%d\n",sizeof(arr)); //20
//定义一个函数给arr获取键盘输入
my_input_array(arr,n);
//定义一个函数遍历数组元素
my_print_array(arr,n);
}
int main(int argc,char *argv[])
{
test03();
return 0;
}
知识点二十三: 二维数组名作为函数的参数
1、如果函数内部想操作(读、写)外部数组的元素,请将外部数组的数组名传递函数
2、二维数组名作为函数的形参会被优化成数组指针
int arr1[5] -----> int *p;
int arr2[3][4] ----> int (*p1)[4];
int arr3[3][4][5] —> int (*p2)[4][5];
#include <stdio.h>
//void my_print_tow_array(int arr[3][4],int cow,int col)
//当二维数组作为函数形参会被优化成数组指针
void my_print_tow_array(int (*arr)[4],int cow,int col) //数组指针每一个元素都是指针,数组每个元素都有对应的地址,刚好保存对应的地址
{
int i,j = 0;
printf("A = %d\n",sizeof(arr));
for(i = 0;i<cow;i++)
{
for(j =0;j<col;j++)
{
printf("arr = %d ",arr[i][j]);
}
printf("\n");
}
}
void test03( )
{
int arr[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
int cow = sizeof(arr)/sizeof(arr[0]); //行数
int col = sizeof(arr[0])/sizeof(arr[0][0]);//列数
printf("A = %d\n",sizeof(arr));
my_print_tow_array(arr,cow,col);
}
int main(int argc,char *argv[])
{
test03();
return 0;
}
知识点二十四:指针作为函数的 返回值
1、函数不要返回普通局部变量的地址
#include <stdio.h>
int* get_addr(void)
{
//int num = 1000;
static int num = 1000; //静态变量 程序结束前不会被释放
return # //不要返回普通局部变量地址(局部变量可能执行后会被释放而存放其他则返回的并不是原来的内容)
}
void test03( )
{
int *p = NULL;
p = get_addr();
printf("*p = %d\n",*p);
}
int main(int argc,char *argv[])
{
test03();
return 0;
}
知识点二十五:函数名代表的是函数的入口地址
函数指针形式:函数返回值类型 (*变量名) (形参类型 ,形参类型 …)
#include <stdio.h>
int my_add(int a,int b)
{
return a+b;
}
void test03( )
{
//my_add代表的是函数的入口地址
printf("%p\n", my_add);
//定一个指针变量保存该函数的入口地址
//函数指针本质:指针变量保存的是函数的入口地址
int (*p)(int ,int ) = NULL; //函数指针
printf("%d\n",sizeof(p));
//将函数指针和函数名建立关系
p = my_add;
printf("%p\n",p);
//函数调用:函数入口地址+()
printf("%d\n",my_add(10,20));
printf("%d\n",p(100,200));
//对函数指针变量取*无意义
}
int main(int argc,char *argv[])
{
test03();
return 0;
}
知识点二十六: 函数指针作为函数的形参
#include<stdio.h>
int my_add(int a,int b)
{
return a+b;
}
int my_sub(int a,int b)
{
return a-b;
}
int my_mul(int a,int b)
{
return a*b;
}
//定义一个函数实现,上述函数的功能
int my_calc(int a,int b,int (*fun_pointer)(int ,int)) //将实参10,20传给形参a,b、函数地址(my_add)传给函数指针(fun_pointer)
{
return fun_pointer(a,b); //返回计算后的结果
}
void test1()
{
printf("%d\n",my_calc(10,20,my_add)); //30
printf("%d\n",my_calc(10,20,my_sub)); //-10
printf("%d\n",my_calc(10,20,my_mul)); //200
}
int main(int argc,char *argv[])
{
test1();
}