目录
深入了解指针(一)
1.指针变量和地址
2.指针变量
3.指针的解引用
4.指针+整数的运用
指针+整数的用法实操
5.void类型指针
6.const修饰指针
6.1const作用
6.2const在*左边
6.3const在*右边
6.4双指针玩法
6.5双const
6.6总结
7.指针的运算
7.1指针+ -整数
7.2指针-指针
指针-指针的意义作用
7.3指针关系运算
8.野指针
8.1指针未初始化
8.2指针越界访问
8.3指针指向的空间已被释放
8.4 避免野指针
9.assert 断言
10.指针的使用和传址调用
10.1传值调用
10.2传址调用
在C语⾔中创建变量其实就是向内存申请空间,⽐如:
个数值有时候也是需要 存储起来,⽅便后期再使⽤的,这个时候就要用到指针变量。
int a = 100;
int* p = &a; //取a的地址存放到p指针这里,也可以说是p指针a的地址,所以在这里p指针的值就是a
printf("*p = %d a=%d \n", *p, a);
指针的变量的引用
int a = 100;
int* p = &a; //取a的地址存放到p指针这里,也可以说是p指针a的地址,所以在这里p指针的值就是a
printf("*p = %d a=%d \n", *p, a);
*p = 0; //*p代表一个值,但p不是
printf("*p = %d a=%d \n", *p, a);
对⽐,下⾯2段代码,主要在调试时观察内存的变化
代码1
int a = 100;
int* p = &a; //取a的地址存放到p指针这里,也可以说是p指针a的地址,所以在这里p指针的值就是a
printf("*p = %d a=%d \n", *p, a);
*p = 0; //*p代表一个值,但p不是
printf("*p = %d a=%d \n", *p, a);
a的内存变化
没变之前 变化之后
代码2
int b = 2546878;
//这个写法有些编译器或者头文件为.cpp报错,.c不会报错
char* q = &b; //这里用char类型的指针来存放b地址
*q = 0; //这里改只能改b内容两位
printf("%d %d", *q,b);
b的内存变化
没变之前 变化之后
运行效果
相对比就可以看出来,int类型的指针存放int类型的变量a,是可以直接全部改变的
但如果是char类型的指针q,只能访问一个个字节
结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。
int x = 10;
//char* e = &x;;//两个写法
char* e = (char *) &x;
int *pi = &x;
printf("x = %p\n", &x); //%p打印地址
printf("e = %p\n", e);
printf("e+1 = %p\n", e+1); //因为pi为char类型指针,所以 + 1地址会增加1个字节
printf("pi = %p\n", pi);
printf("pi = %p\n", pi+1); //区分pi+1,因为pi为int类型指针,所以+1地址会增加4个字节。
printf("*pi = %d\n", *pi);//*pi代表值,而p则为地址。
printf("*pi+1= %d\n", *pi + 1);
效果如下图
比如给数组初始化
为什么要指向a[0]?
因为一个数组的地址他是连续的
当你指向第一个地址那么后面的地址肯定是连在一起的,
相当于找到了火车头,那么后面的车厢就可以直接找到了
//比如给数组初始化
//为什么要指向a[0]
//因为一个数组的地址他是连续的
//当你指向第一个地址那么后面的地址肯定是连在一起的,
// 相当于找到了火车头,那么后面的车厢就可以直接找到了
int arr[10] = { 0 };
int* pa = &arr[0];
for (int i = 0; i < 10; i++)
{
*pa = 1;
pa = pa + 1; //这样利用int类型的指针就可以指向下一个了。
//pa++;一样的写法
}
pa = &arr[0]; //这里重新指向数组的头,因为经过上面的初始化,指针已经指向最后了。
for (int i = 0; i < 10; i++)
{
printf(" %d ", *pa);
pa = pa + 1;
}
//void*类型指针
int a = 6;
void* pa = &a;
printf("%p\n", pa);
printf("%p\n\n", a);
char ch = 'a';
void* pb = &ch;
printf("%p\n", pb);
printf("%p\n", ch);
void指针可以接收不同类型的变量的地址而不会报错,其主要用法就是到后面函数的传参和接受参数的时候用void类型的指针来接收参数。
下面分几种情况讨论
//const修饰指针
//const作用就是给指定的变量加上保护,让其不能被修改,跟常量相似,但其本身还是变量。
const int c = 20;
//c = 20;//这样写会报错
//但是可以通过下面进行间接修改,相当于走后门。
int* p = &c;
*p = 5;
printf("%d \n", c);
//讨论const所在的位置,不同的效果。
const int d = 50;
int e = 20;
const int* pm = &d; //此时const限定了*p让其不能修改,不能通过pm来修改pm指向的空间内容
//*pm = 30;//报错
const int h = 50;
int * pm1 = &h;
//虽然不能修改pm1指向的内容,但是不会限制pm1指向其他
*pm1 = &e;
const int q = 50;
int* const pm2 = &q; //在这里const限制的是p,而不是*p
*pm2 = 20;//这个时候就可以修改了
//要理解1.pm2里面存放的是地址(q的地址)
//2.pm2也是一个变量,他也有自己的地址
//3.而*pm2是pm2指针指向的空间内容。
const int q = 50;
int* const pm2 = &q; //在这里const限制的是p,而不是*p
*pm2 = 20;//这个时候就可以修改了
int* Pa = pm2;
*Pa = 10;
printf("%d %d %d", *pm2, *Pa,q);
const int w = 50;
const int* const pm3 = &w;
const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本⾝的内容可变。
const如果放在* 的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变
//逆序打印
int arr[10] = { 0 };
int* p = &arr[0];
for (int i = 0; i <10; i++)
{
scanf("%d", p+i);//输入来实现
}
p = &arr[9];
for (int i = 9; i >= 0; i--)
{
printf("%d ", *p);
p--;
}
//指针-指针
//指针-指针 =整数
//类似于,日期-日期 =天数
//日期-天数 = 日期
int ax[10] = { 0 };
printf("%d", &ax[9] - &ax[0]); //=9 指针-指针的绝对值只会得到相隔的元素个数。
模拟strlen的实现,strlen功能是求出字符串的长度,不包括\0.
#include
#include
int ax_strlen(char *s )//指针s指向第一个字符的地址
{
/*int count = 0;
while (*s !='\0')
{
count++;
s++;
}
return count;*/
//第二个写法
//思路是要找到第一个字符的地址以及\0的地址,然后两个指针相减即可。
char* weikun = s;
while (*s != '\0')
{
s++;
}
return s - weikun;
}
int main()
{
//指针的运用
//求字符串的长度
int len = strlen("sdasd");
printf("%d \n", len);
int len1 = ax_strlen("sdasdsjhfhjdhff"); //这里传过去的是首个字符的首地址,然后用指针来指向第一个的地址。
printf("%d \n", len1);
return 0;
}
//指针的比较
int ax[10] = { 0,1,2,3,4,5,6,7,8,9 };
int* p = ax;//这个等价于&ax[0]
//int *p = &ax[0];
int m = sizeof(ax) / sizeof(ax[0]);
printf("%d\n", m);
//ax是数组名,数组名其实就是数组首元素的地址。
while (p < ax+m )//指针+整数=指针
{
printf("%d ", * p);
p++;
}
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针的形成原因有
#include
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
#include
int main()
{
int arr[10] = {0};
int *p = &arr[0];
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
#include
int* test()
{
int n = 100;
return &n;
}
int main()
{
int*p = test();
printf("555"); //在这里加一个printf容易看出来*p得不到10,这是关于栈销毁方面知识。
printf("%d\n", *p);
return 0;
}
不使用指针时候要置为null
如 int *p=null;
assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报 错终⽌运⾏。这个宏常常被称为“断⾔”。
使用方法,用来检指针,或者用于调试都可以,下面举例检验指针的使用。
int a = 10;
int* p = &a;
p = NULL;
//使用记得加上头文件#include
assert(p != NULL);//如果符合条件,那么无影响,如果不符合就会报错,程序停止运行,并报出错误位置。
如要不使用assert加上这个即可,#define NDEBUG
使⽤ assert() 有⼏个好处:它不仅能⾃动标识⽂件和 出问题的⾏号,还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制。
函数又可以分为,传值调用和传参调用
区分何时使用这两种调用
函数内部要改变函数外部的内容时候,那么就要用到传址调用
函数内部只需要接受外部的内容是,这个时候用传值调用。
#include
int swap(int x, int y)
{
return x + y;
}
int main()
{
int a = 10;
int b = 20;
int len = swap(a, b);//传值调用
printf("len = %d\n", len);
}
非指针不可例子,写⼀个函数,交换两个整型变量的值
#include
void swap1(int x, int y)
{
int z = 0;
z = x;
x = y;
y = z;
}
int main()
{
int a = 10;
int b = 20;
Swap(a, b);
printf("a=%d b=%d", a, b);
}
很显然,并得不到我们想要的结果,为什么呢?
经过调试可以知道,a、b和x、y的地址都不一样,改变的x,y,但是a和b并没有改变,所以传值是无法实现的,只有通过传a和b的地址,然后再修改才可以。下面传址调用来实现。
#include
void Swap(int* x, int* y)
{
int z = 0;
z = *x;
*x = *y;
*y = z;
}
int main()
{
int a = 10;
int b = 20;
Swap(&a, &b);
printf("a=%d b=%d", a, b);
}