C语言-指针(一)
1、指针是什么
在计算机科学中,指针(Pointer)是变成语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为”指针“。意思是通过它能找到以它为地址的内存单元。
那我们就可以这样理解
内存:
内存 | |
---|---|
一个字节(8个bit) | 0xFFFFFFFF |
一个字节(8个bit) | 0xFFFFFFFE |
一个字节(8个bit) | |
… | |
一个字节(8个bit) | 0x00000002 |
一个字节(8个bit) | 0x00000001 |
将每一个内存单元的编号叫做内存地址
内存单元的编号是怎么产生的?
如果是32位机器,通电以后会产生正负电信号,从而产生32个01组成的二进制序列
int main(){
int a = 10;//int类型占4个字节
int * pa = &a; //拿到的是a的四个字节中de int 说明pa指向的变量是int类型
return 0;
}
对于32位的机器,假设有32跟地址线,那么假设每个根地址线在寻址的是产生一个电线号正电/负电(1或者0)
那么32跟地址产生的地址就会是
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
00000000 00000000 00000000 00000010
…
11111111 11111111 11111111 11111111
这里就有2的32次方个地址
2、指针类型
int main(){
int * pa;
char * pc;
float * pf;
printf("%d\n",sizeof(pa));
printf("%d\n",sizeof(pc));
printf("%d\n",sizeof(pf));
return 0;
}
int main(){
//0 1 2 3 4 5 6 7 8 9 a b c d e f
//11111111 //从右向左数 是第几位权重就是几 值就是2的多少次方
// 8421
//8 + 4 + 2 + 1 = 15 = f
int a = 0x11223344;
return 0;
}
1、指针类型决定了:指针解引用的权限,比如char只解引用一个字节 int 解引用4个字节
int main(){
int arr[10] = {0};
int *p = arr;
char *pc = arr;
printf("%p\n",p); //004FFC40
printf("%p\n",p+1); //004FFC44
printf("%p\n",pc); //004FFC40
printf("%p\n",pc+1); //004FFC41
//由以上代码可以看出 char类型的指针+1 是加了一个字节指针变量
//int类型的+1是加了四个字节的指针变量
return 0;
}
2、指针类型决定了,指针走一步,能走多远(步长),char+1 走一步 int+1走4步
int main(){
int arr[10] = {0};
char *p = arr;
int i = 0;
for (i=0;i<10;i++){
*(p+i) = 1;
}
return 0;
}
总结:指针的类型决定了,对指针引用的时候由多大的权限(能操作几个字节)。比如:char的指针解引用就只能访问一个字节,而int的指针接应用可以访问四个字节
3、野指针
概念:野指针就是只恨向的位置是不可知的(随机的,不正确的,没有明确限制的)
野指针成因
1、指针未初始化
#include<stdio.h>
int main(){
int *p; //局部变量指针未初始化,默认为随机值,如果不初始化的话这里会填充一个随机值,*P以为这个随机值就是一个指针地址
*p = 20;//将这个随机的地址中放入一个值,这样操作是非法访问地址,因为这个指针地址不是*p的
return 0;
}
2、循环访问越界产生野指针
int main(){
int arr[10] = {0};
int * p =arr;
int i = 0;
for(i = 0;i<=10;i++){
*p = i;
p++;
}
return 0;
}
3、空间被释放的情况下,之前定义的指针变量就失效了,就成为一个野指针
//代码逻辑是 进入main函数以后调用test方法,获得一个内存地址,然后将这个内存地址赋值给指针变量*p,但是当test返回这个地址以后就销毁了,也就是说此时返回的这个指针地址是个野指针,因为这个地址以及还给操作系统了。
int * test(){
int a = 10;
return &a;
}
int main(){
int *p = test();
*p = 20;
return 0;
}
如何避免野指针
1、指针初始化
2、小心指针越界
3、指针指向空间释放及时设置NULL
4、指针使用之前检查有效性
int main(){
//当前不知道p应该初始化为说明地址的时候,直接初始化为NULL
int * p = NULL;//null的本质是0或者 void* 类型的指针 也是0
//明确找到初始化的值
int a= 10;
int * ptr = &a;
if(p != NULL){
*p = 10;
}
//c语言本身是不会检查数据的越界行为的
return 0;
}
一个指针如果不知道要指向那个内存地址的时候就设置成null ,当它指向的空间被释放掉以后,也把它置成null
4、指针的运算
- 指针 ± 整数
- 指针 - 指针
- 指针的关系运算
//指针 +- 整数
#define N_VALUES 5 //定义一个常量
float values[N_VALUES]; //定义一个float类型的数组
float *vp; //vp指针 没有初始化
//指针 +- 整数:指针的关系运算
for (vp = & values[0]; vp < &values[V_VALUES];)
{
*vp++ = 0;
}
int main(){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = arr;
int * pend = arr + 9;//指向10
while(p<=pend){
printf("%d\n",*p);
p++;
}
return 0;
}
int main(){
int arr[] = {1,2,3,4,5,6,7,8,9,10};
printf("%d\n", &arr[9] - &arr[0]); //获取下标9和0的数据的内存地址进行相减
return 0;
//指针-指针 = 得到两个指针之间元素的个数
}
#include<string.h>
int my_strlen(char* str){
int count = 0;//定义计数器
while(*str != '\0'){
count++;
str++;
}
return count;
}
int main(){
//strlen(); - 求字符串长度
//递归
int len = my_strlen("abc");
printf("%d\n",len);
return 0;
}
#include<string.h>
int my_strlen(char* str){
char * start = str;
while(*str != '\0'){
str++;
}
return str - strart;//指针-指针
}
int main(){
//strlen(); - 求字符串长度
//递归
int len = my_strlen("abc");
printf("%d\n",len);
return 0;
}
for(vp = &values[N_VALUES-1];vp >= &values[0]; vp--){
*vp = 0;
}
实际上在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样的写法,因为标准并不保证它可执行。
标准规定:允许指向数组元素的指针于指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许于指针第一个元素之前的那个内存位置的指针进行比较。
5、指针和数组
数组名是说明?我们看一个例子
#include <stdio.h>
int main(){
int arr[10] = {1,2,3,4,5,6,7,8,9,0}; //数组名是数组首元素的地址 如果首元素是多个字节那么就是首元素的首字节的地址
printf("%p\n",arr);
printf("%p\n",&arr[0]);
return 0;
}
int main(){
int arr[10] = {0};
int * p = arr;
int i = 0;
for(i = 0; i < 10; i++){
printf("%p <==> %p\n", &arr[i], p + i);
*(p + i) = i;
}
for(i = 0; i < 10; i++){
printf("%d",*(p+i));
}
return 0;
}
int main(){
int arr[10] = {0};
int* p = arr;//数组名
//arr[2] <==> *(arr+2) <==> *(p+2) <==> *(2+p) <==> *(2+arr) == 2[arr]
//2[arr] <==> *(2+arr)
return 0;
}
6、二级指针
int main(){
int a = 10;
int * pa = &a;//pa是指针变量,一级指针
int **ppa = &pa;//pa也是个变量,&pa去除pa在内存中起始地址
int ***pppa = &ppa;//三级指针,依此类推
return 0;
}
a 变量里 放 10 a的内存地址是0x0012ff40
然后初始化一个指针变量pa 存放内存地址0x0012ff40
与此同时指针变量pa也是由地址的,假设是0x0012ff36
再初始化一个指针变量ppa 将pa的地址放入ppa叫做二级指针
7、指针数组
int main(){
int arr[10];//整型数组 - 存放整型的数组就是整型数组
char ch[5];//字符数组 - 存放的是字符
//指针数组 - 存放指针的数组
int* parr[5];//整型指针的数组
char* pch[5];
return 0;
}