C语言指针
指针基础
一.基础知识
- 内存单元的编号 == 地址 == 指针
- 取地址操作符&
- 解引⽤操作符(*)
- 指针变量 int* pa = &a;
- *&a=0; //等效于a=0;
二.指针变量的大小-------多少位机器
32位(x86)机器有32根地址总线,每根地址线出来的电信号转换成数字信号后 是1或者0,那我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4 个字节才能存储。
如果指针变量是⽤来存放地址的,那么指针变的大小就得是4个字节的空间。
同理64位机器,有64根地址线,⼀个地址就是64个⼆进制位组成的⼆进制序列,存储起来就需要 8个字节的空间,指针变的大小就是8个字节。
结论:
• 32位平台下地址是32个bit位,指针变量大小是4个字节
• 64位平台下地址是64个bit位,指针变量大小是8个字节
• 注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,大小都是相同的。
三.指针操作字符大小
(1)指针的类型决定了,对指针解引用的时候有多大的权限(⼀次能操作几个字节)。
//代码1
#include <stdio.h>
int main()
{
int n = 0x11223344;
int *pi = &n;
*pi = 0;
return 0;
}
//代码2
#include <stdio.h>
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;
*pc = 0;
return 0;
}
代码1会将n的4个字节全部改为0,但是代码2只是将n的第⼀个字节改为0。
指针通过解引用去更改地址内的变量的大小,是通过读取字符类型的大小来确定更改的长度
(2)指针±整数
- char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。
结论:指针的类型决定了指针向前或者向后⾛⼀步有多大(距离)。
四.void* 指针(泛型指针)
- 指针可以⽤来接受任意类型地址
- 指针不能直接进行指针的±整数和解引用的运算
#include <stdio.h>
int main()
{
int a = 10;
void* pa = &a;
*pa = 10;//err错误
return 0;
}
五.const修饰指针
#include <stdio.h>
int main()
{
const int n = 0;
int*p = &n;
*p = 20;
return 0;
}
可以通过地址来绕过改const修饰的a
(1)int * const p =&a
int main()
{
int a=0;
int b=1;
int *const p=&a;
p=&b;//err错误
return 0;
}
const限制的是p的指向只能是a的地址
- 指针变量不能在指向别的变量
- 可以修改指针变量指向的变量的数值
(2)int const * p =&a
int main()
{
int a=0;
int b=2;
int *const p=&a;
*p=10;//err错误
*p=&b;
return 0;
}
const限制的是p的指向的a的值不能改变
- 指针变量可以在指向别的变量
- 不能修改指针变量指向的变量的数值
(3)int const * const p=&a
- 都不能变
六.指针运算
(1)指针± 整数
*type p;
p+1–> 跳过 1*sizeof(type)
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; i++)
{
printf("%d ", *(p+i));//p+i 这⾥就是指针+整数
}
return 0;
}
还可以读取数组
(2)指针-指针
- 算两个地址的距离
七.野指针
(1)产生原因
- 指针未初始化
*int p;//局部变量指针未初始化,默认为随机值
- 指针越界访问
在数组中,p指向的地址超过数组的范围
- 指针指向的空间释放
int* test()
{
int n = 100;
return &n;
}
int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}
(2)规避
- NULL
- 不知道指针应该指向哪⾥,可以给指针赋值NULL.
int * p= NULL;
这意味着指针
p
没有指向任何有效的内存地址,它是一个空指针。使用空指针可以防止未初始化的指针访问无效的内存,这是一种避免程序崩溃和潜在安全问题的好习惯。
- 遇到NULL指针,编译器不去访问
- 不能解引用
*p=100;//err
- assert断言
- assert.h 头文件定义了宏 assert() ,用于在运行时确保程序符合指定条件,如果不符合,就报 错终止运行。
assert(p != NULL);
- 加入宏定义
#define NDEBUG
可以关闭assert() 的机制,让assert不起作用
八.指针的使用和传址调用
(1)strlen
-
功能:是求字符串⻓度,统计的是字符串中 \0 之前的字符的个数
size_t strlen ( const char * str );
-
my_strlen函数
int my_strlen(char * str)
{
int count = 0;
while(*str)//字符串解引用当为'/0'时为假
{
count++;
str++;
}
return count;
}
int main()
{
int len = my_strlen("abcdef");
printf("%d\n", len);
return 0;
}
(2)交换a,b的值----传址调用
void Swap(int*px, int*py)
{
int tmp = 0;
tmp = *px;
*px = *py;
*py = tmp;
}
int main()
{
int a =5; int b = 10;
Swap1(&a, &b);
return 0;
}
- 利用地址去修改,可以直接对实参修改
九.typedef关键字
typedef unsigned int uint;
//将unsigned int 重命名为uint
-
觉得 unsigned int 写起来不⽅便,就用typedef能写成 uint
-
数组指针
typedef int(*pt)[5];//pt就是int(*)[5] int (*p)[5];//等价于pt p;
-
函数指针
typedef void(*p_t)(int);//pt就是void(*)[int] void(*p)(int);//等价于pt p;
指针和数组
一.取数组地址
- 一般都是数组首元素(第一个元素)的地址
- arr 首元素的地址等同于&arr[0]
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("&arr[0] = %p\n", &arr[0]);
printf(" arr = %p\n", arr );//两个的含义是相同的
-
特殊
-
sizeof(arr),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小, 单位是字节
-
&arr,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素 的地址是有区别的)
-
二.指针访问数组
int arr[10] = {0};
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
int* p = arr;
- 输入
for(i=0; i<sz; i++)
{
scanf("%d", p+i);//scanf("%d", arr+i); //scanf("%d", &arr[1])//也可以这样写
}
为什么不是scanf(“%d”, *p+i);呢?
在C语言中,数组名作为表达式使用时,它会被隐式地转换为指向数组首元素的指针。因此,
arr
和p
在这里都是指向数组首元素的指针。当你写scanf("%d", arr+i)
或者scanf("%d", p+i)
时,你实际上是在进行指针算术,告诉scanf
函数跳过数组首地址后的i
个整数的位置去读取数据。这里有几个关键点需要理解:
arr+i
和p+i
都是指针运算,它们都表示数组的第i+1
个元素的地址。*p+i
这种写法是不正确的,因为*p
将指针p
解引用,得到它指向的值(即数组的第一个元素),然后+i
尝试将这个值与一个整数相加,这在C语言中是不允许的,因为不能将整数与整数的值相加来得到一个新的地址。
scanf(“%d”, &arr[1]) 和scanf(“%d”, arr[1])差别
在C语言中,
scanf("%d", &arr[1])
和scanf("%d", arr[1])
表现相同,但实际上并不相等
scanf("%d", &arr[1] )
:
- 这行代码使用取地址运算符
&
来获取数组arr
第二个元素的地址,并将这个地址传递给scanf
函数。scanf
函数期望一个指向整数的指针,以便它可以将用户输入的整数值存储在这个内存位置。scanf("%d", arr[1] )
:
- 这行代码直接使用数组名
arr
和索引[1]
来访问数组的第二个元素。- 根据C语言的语法规则,数组名作为表达式时会被解析为指向数组首元素的指针,所以
arr[1]
实际上等同于(*(arr + 1))
,即解引用指针arr + 1
来获取元素的值。虽然在实际使用中,如果只关心
scanf
函数的输入行为,两者可能看起来表现相同,因为它们都是将用户输入的值存储到数组的第二个元素中。但是,从语法和类型安全的角度来看,使用&arr[1]
是更明确和正确的方法,因为它直接传递了一个指向整数的地址给scanf
函数。
在C语言中,数组名
arr
在大多数情况下会被隐式地转换为指向数组首元素的指针。但是,这种转换仅适用于那些期望指针参数的函数调用,例如scanf
函数。
所以可以理解为scanf特殊,只有scanf可以用**scanf("%d", &arr[1] )
**😕/暂时这么理解
- 输出
for(i=0; i<sz; i++)
{
printf("%d ", *(p+i));//printf("%d ", p[i]);//*(arr+1)
}
- arr[i]==*(arr+1)
三.数组指针变量 &arr
int (*p)[10]=&arr;
int *p1[10];//存放指针的数组
int (*p) [10] = &arr;//指向数组-----数组指针
| | |
| | |
| | p指向数组的元素个数
| p是数组指针变量名
p指向的数组的元素类型
四.数组传参的本质
(1)一维数组
void test(int arr[])//test(int*arr)
{int sz2 = sizeof(arr)/sizeof(arr[0]);}
//sz等于1因为只有首元素的地址
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
test(arr)
return 0;
}
-
本质上是指针变量,只是为了好看可以写成数组形式
-
形参的数组不会单独创建空间,用的是同一个数组
(2)二维数组
void test(int (*arr)[5], int r, int c)//test(int a[3][5], int r,int c)
{int i = 0;int j = 0;
for(i=0; i<r; i++){ for(j=0; j<c; j++){
printf("%d ", *(*(arr+i)+j));} }//太深奥了!!!arr[i][j]
}
int main()
{
int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}};
test(arr, 3, 5);
return 0;
}
- 二维数组可以理解为一个一维数组每个元素都存有二维数组中一个横的数组
- 函数传参传过去的是首元素的地址(首元素是一个数组),所以用数组指针(&arr)类型
第四横 printf("%d ", *(*(p+i)+j));//太深奥了!!!
-
arr+i
计算出指向数组第i行的指针 -
*(arr+i)拿到第i横数组名==arr[i]
-
*((arr+i)+j)元素值arr[i] [j]
五.二级指针
int a=10;
int * p=&a;
int ** pp=&p//int **二级指针类型
六.指针数组
存放指针的数组
*int parr[];
- 模拟二维数组--------指针数组中每一个元素都是指针----都来映射一个数组
#include <stdio.h>
int main()
{
int arr1[] = {1,2,3,4,5};
int arr2[] = {2,3,4,5,6};
int arr3[] = {3,4,5,6,7};
//数组名是数组⾸元素的地址,类型是int*的,就可以存放在parr数组中
int* parr[3] = {arr1, arr2, arr3};
int i = 0;
int j = 0;
for(i=0; i<3; i++){
for(j=0; j<5; j++){
printf("%d ", parr[i][j]);
} }
return 0;
}
指针和函数
一.函数指针
int (*p) (int x, int y) = &test
| | |
| | |
| | p指向函数的参数类型和个数的交代
| 函数指针变量名
p指向函数的返回类型 指针类型int(*)(int,int)
include <stdio.h>
int test(int x, int y)
int main()
{
int(*p)(int, int) = Add;
int d=test(2,3);
int c=(*p)(2,3);//也可以写成int c=p(2,3);
reyurn 0;}
二.函数指针数组
- 把函数的地址存到⼀个数组
int(*parr[4])(int,int)={test1,test2,test3,test4}//parr是函数指针数组
//tset1是函数
- 应用
for(int i=0;i<4;i++){
int r=(*parr)[i](1,2);
}
三.转移表
计算器(函数)
int add(int a, int b){return a + b;}
int sub(int a, int b){return a - b;}
int mul(int a, int b){return a * b;}
int div(int a, int b){return a / b;}
int main()
{int x, y;
int input = 1;
int ret = 0;
do{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %d\n", ret); break;
case 2:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %d\n", ret); break;
case 3:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %d\n", ret); break;
case 4:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %d\n", ret); break;
case 0:
printf("退出程序\n"); break;
default:
printf("选择错误\n"); break;
}
} while (input);
return 0;
}
函数指针(回调函数)
void a(int (*p)(int ,int)){
int x=0;
int y=0;
int z=0;
printf( "输⼊操作数:" );
scanf( "%d %d", &x, &y);
z=pf(x,y);
printf("%d\n",z);
}
int main()
{
int input = 1;
do{
scanf("%d", &input);
switch (input)
{
case 1:
a(Add); break;
case 2:
a(sub); break;
case 3:
a(mul) break;
case 4:
a(div) break;
case 0:
printf("退出程序\n"); break;
default:
printf("选择错误\n"); break;
}
} while (input);
return 0;
}
- 通过a函数指针调用
改写指针数组(移列表)
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
do{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf( "请选择:" );
scanf("%d", &input);
if (input <= 4 && input >= 1){
printf( "输⼊操作数:" );
scanf( "%d %d", &x, &y);
ret = (*p[input])(x, y);
printf( "ret = %d\n", ret);
}
else if(input == 0)
{
printf("退出计算器\n");
}
else
{
printf( "输⼊有误\n" );
}
}while (input);
return 0;
}
- 能把函数统一的编号管理----厉害!
指针和字符
字符指针
const char* p = "hello world.";//指针p指向字符串的首地址
printf("%s\n", p);
- 和数组类似,但是不能通过解引用去更改字符串
- 当两个相同的字符串创建的时候,只创建一个,节省空间
const char *str3 = "hello world.";
const char *str4 = "hello world.";//str3==str4