指针: C语言的灵魂
指针是啥?
1. 指针变量 : 一个变量 存放一个内存的地址
2. 一个内存地址 指针常量指针变量的定义:
存储类型 指针指向的空间的数据类型 * 变量名;
示例:
int *p = &a;指针变量的 赋值: 类型匹配才可以赋值
物理意义: 与指定的内存 建立联系&地址运算: 变量可取地址 有内存的量
得到变量的内存地址野指针: 一个指针 指向的内存 不可知 或 不确定 或 非法(不可访问)
空指针: 一个指针 指向内存 0地址(不允许访问) 即这个指针中的值是0 int *p = NULL;
宏 NULL 空指针 (void *)0 通常在程序中 表示一个指针 没有建立任何指向关系void * 类型 : 表示 万能指针, 一个可以指向 任意类型的指针
指针的访问: * 指针取值运算符
*指针变量 即等同于 指针指向的目标(对象)
1 * 只能对指针操作 只能对有具体类型的指针
2 指针必须有建立指向关系 不能对空指针 野指针进行*操作
*空指针 段错误/空指针异常
*野指针 结果未知 大概率段错误
void * 类型的指针 可以赋值 void *p = &a; *p 不行 *(int*)p 可以的
不能*操作 通常是进行强制类型转换 为指定类型的
int b;
int *p = &b;
*&b; 等同于 b
* 是 & 的逆运算
&*p; 等同于 &b
& 是 * 的逆运算指针的算数运算:
指针 +或- 一个整数 其结果仍然是一个指针
其运算本质就是 地址的移动 + 向大地址方向移动 - 小地址方向移动
其移动的字节大小 由其指向的类型长度决定 +1 / -1 移动一个指向的类型
指针的 ++ -- 运算 p++; *p++; (*p)++;#include <stdio.h> int a =5; int main() { int b; int *p = &a; //定义一个指针变量并初始化 int **pp = &p; int *x; // 野指针 x = NULL;// 空指针 //指针的表示 printf("&a=%p\n",&a); printf("p= %p\n",p); // 打印变量p中存放的地址 p = &b; //给p赋值 printf("p= %p\n",p); // 打印变量p中存放的地址 printf("&p=%p\n",&p); // 打印变量p的地址 printf("pp=%p\n",pp); // 打印变量pp中存放的地址 //指针的访问 *p = 20; //*p 等同于b *p = 20; 等同于 b = 20; 给*p赋值 printf("b=%d\n",b); printf("*p=%d\n",*p); printf("**pp=%d\n", **pp); // **pp 等同于 *p 等同于b printf("*pp=%p\n", *pp); // *pp 等同于p printf("*&b=%d\n", *&b ); printf("&*p=%p\n", &*p ); //空指针 //int *q = NULL; //printf("%d",*q); 段错误 //void * void *q = p; printf("q=%p\n",q); //printf("*q=%d\n",*q); //语法错误,无法编译 printf("*(int*)q=%d\n",*(int*)q); // return 0; }
指针 与 指针的 运算
1. 大地址 - 小地址 结果是一个整数 其大小为 这两个地址之间相隔的 指向的类型个数
(1) 这两个地址 必须在同一段连续的内存空间上
(2) 这两个地址 类型要一致
2. 关系运算 本质是 判断 这个两个地址 在内存中的位置
p > q p地址 在 q地址的 大方向
p < q p地址 在 q地 址的 小方向
p == q p和q指针指向同一个内存
p != q p和q指针指向不同的内存
p >= q p地址 在 q地址的 大方向 或 相同
p <= q p地址 在 q地址的 小方向 或 相同
一级指针和一维数组:
数组名可以当指针用
指针变量 可以做数组用
区别在于 指针变量 可以重新赋值 而数组名不行
int arr[5] = {1,2,3,4,5};
int *p = arr; // p = &arr[0] 等同
p[0] == arr[0] == *arr == *p //等同
p[1] == arr[1] == *(arr+1) == *(p+1)
有如下定义:
int a[10]={0,1,2,3,4,5};
int *p = a;
假设每个表达式中p=a;求表达式代表的值?
*p=? a[0] == 0
*a=? a[0] == 0
p[0]=? a[0] == 0
a[0]=?
*p++=? == *(p++) == 先*p用 作为表达式的值, 然后 p=p+1
*(p++)=?
*p+1=? a[0] + 1 == 1
*(p+1)=? a[1] == 1
*a+1=? a[0] + 1 == 1
*(a+1)=? a[1] == 1
a++; 不行 a数组名 常量指针 不能赋值
练习: 数组倒叙
void arr2rra(int *start, int *end) //倒叙 从start到end之间的 数组元素, 不能定义其他变量
{}
int main()
{
int arr[] = {1,2,3,4,5};
show_arr(arr,5);
arr2rra(arr, arr+4);
show_arr(arr,5);
return 0;
}
#include <stdio.h>
void show_arr(int *arr, int len )
{
for (len=len-1;len>=0;len--)
printf("%d",arr[len] );
}
int main()
{
int arr[]={1,2,3,4,5};
show_arr(arr,5);
printf("\n");
return 0;
}
指针与字符串
char s[] = "hello"; //s是一个数组 将"hello"复制一份到s数组中
cosnt char *p = "hello"; // p是一个指针变量 指向常量区字符串
*p求字符串的长度:
int len(const char *s)
{
char *p = s;
while(*p) p++;
return p-s;
}const 修饰指针:
cosnt char *p; *p 只读 p 可读可写
char * const p; *p 可读可写 p 只读
const char * const p ; *p 只读 p 只读
总结指针的 5个运算:
= 赋值运算 建立指向关系
* 取指向对象 *p
+/- n 地址移动
p-q 指针相减
p>q 关系运算指针占用的内存大小:
指针变量指向的类型 不影响其 占用的内存大小
若 是32位机器 地址大小为 4字节
若 是64位机器 地址大小为 8字节
指针数组 与 二级指针
指针数组: 就是一个数组其元素是 指针
如图所示 :
ps 是一个 常量字符串指针数组
定义:
存储类型 元素类型 数组名[元素个数] = {};
auto cosnt char * ps[6] = {"Beijing", "Moscow", "New York", "","", NULL};遍历指针数组:
for(int i= 0; i<5; i++)
printf("%s ",ps[i]);指针方式遍历:
//二级指针
const char ** pps = ps;//遍历指针数组:
for( int i = 0; i<5; i++)
printf("%s ",*pps++);
printf("\n");
#include <stdio.h> //函数遍历任何二维数组 void show(int *arr, int hang, int lie) { for(int i=0;i<hang;i++) { for(int j=0;j<lie;j++) printf("%d ",arr[ i * lie +j ]); printf("\n"); } } //函数遍历任何二维数组 void show2(int *arr, int hang, int lie) { //了解 int (*p)[lie] = (int (*)[lie])arr; for(int i=0;i<hang;i++) { for(int j=0;j<lie;j++) printf("%d ",p[i][j]); printf("\n"); } } int main() { //数组指针 与 二维数组: //二维数组: int arr[2][3] = {1,2,3,4,5,6}; //数组指针: 含义: 一个指针 指向一个一维数组 int (*p)[3] = arr; // p = &arr[0]; printf("p=%p\n",p); printf("&arr[0]=%p\n",&arr[0]); printf(" arr =%p\n", arr); printf(" arr[0]=%p\n", arr[0]); //通过p遍历数组arr for(int i=0;i<2;i++) { for(int j=0;j<3;j++) printf("%d ",*(*(p+i)+j)); printf("\n"); } //二维数组 展平遍历 int * q = (int*)arr; for(int i=0;i<6;i++) printf("%d ", *q++); printf("\n"); show2((int*)arr, 2, 3); return 0; }
练习:
char *a[3] = {"hello","world","ok"};
char **p = a;
假设每个表达式中p=a;求表达式代表含义或值?
a[0] == "hello" == char* == %s
*p == a[0]
**p == *a[0] == 'h'
*a == a[0]
**a == *a[0] == 'h'
*p++ == a[0] 然后 p=p+1
*(p++) == *p++
**(p+1)) == *"world" == 'w'
*(*(p+1)+1) == *(a[1]+1) == 'o'
(*p)++ 考虑p能否进行该操作? 可以操作
先*p == a[0] 然后 a[0] = a[0]+1; a[0] ---> "ello"数组指针 与 二维数组:
二维数组:
int arr[2][3] = {1,2,3,4,5,6};
数组指针: 含义: 一个指针 指向一个一维数组
int (*p)[3] = arr; // p = &arr[0];&arr[0]=0x7ffc4307c1a0 // 数组指针类型
arr =0x7ffc4307c1a0 // 数组指针类型
arr[0]=0x7ffc4307c1a0 // 常量int类型指针
&arr[0][0] =0x7ffc4307c1a0 // 常量int类型指针问p+1是什么? p+1 == arr[1]
练习:
int a[2][3];
int (*p)[3] = a;
假设每个表达式中p=a;求表达式代表含义?
a[0] 0号元素是一个一维数组
*p == a[0]
**p == a[0][0]
*a == a[0]
**a == a[0][0]
*p++ == 先*p == a[0] 然后 p = p+1; p----> a[1]
*(p++) == *p++
**(p+1) == *a[1] == a[1][0]
*(*(p+i)+j)
== *(a[i]+j) == a[i][j]
(*p)++ 考虑p能否进行该操作? 不能
先*p == a[0] 然后 然后 a[0] = a[0]+1; 数组名不能赋值指针与函数:
指针函数:
本质是一个函数, 返回值是一个指针
函数指针:
本质是一个指针, 指向一个函数代码段:
存储代码函数名:
1. 代指这个函数本身
2. 代指这个函数的首地址
函数类型:
除去函数名 以及形参名等 名字 剩下的就是函数的类型函数指针定义:
指向的函数返回值类型 (*函数指针名)(函数形参类型列表) = 对应类型的函数;
函数指针应用: 回调函数 多用于写接口
接口: API
回调函数: 一个函数 其参数 有函数指针类型
案例: 要求写一个函数, 参数传入一个数组, 要求输出 满足调用者条件的数?
void func1(int arr,int len, ????)
创建线程的函数: 了解
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
void *(*start_routine) (void *):
start_routine 是一个指针 指向一个函数 该函数类型 为 void * (void *)
void * (void *) 是 一个函数类型 这个类型的函数 有一个 void*类型的返回值
有一个 void * 的形参#include <stdio.h> //函数指针 int func(char a)// 函数类型 int (char) { printf("hello func\n"); } //定义一个函数指针 存放函数的地址 int (*fp)(char) = &func; int main() { (&func)('A'); printf("func=%p\n",func); printf("&func=%p\n",&func); printf("fp =%p\n",fp); printf("main=%p\n",main); //通过函数指针 调用指向的函数 (*fp)('B'); fp('B'); return 0; }