C语言指针
- 指针是什么
- 指针和指针类型
- 野指针
- 指针运算
- 指针和数组
- 二级指针
- 指针数组
指针是什么?
指针(Pointer)是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。
换句话说就是可以通过指针找到以它为地址的内存单元。
理解:内存图解。
指针是个变量,存放内存单元的地址(编号)。
int main(){
int a = 10;
int* p = &a;
}
总结:指针就是变量,内容是地址。(存放在指针中的值被当做地址处理)
指针的大小
在32为计算机上指针大小4字节。
在64为计算机上指针大小8字节。
指针和指针变量
关于地址
printf("%p \n",&a);//%p地址格式 &a取a的地址
int* p = &a;
//int*指针类型
//p 指针变量
//&a 取地址
使用
*p //解引用操作符
int a =10; //在内存中存储10 还有char*等类型
int* p = &a;//定义指针,位置为a的内存
*p = 20; //更改指针指向内存的 值
printf("a= %d",a);//结果为a=20
int* p的理解 p是int类型的一个指针(仅此而已),一般*p指向的也是一个int型的
1. 指针类型决定了指针进行解引用操作的时候,能访问空间的大小
int main(){
int n = 0x112233;
char* p = (char*)&n;
int* pi = &n;
*pc = 0;
*pi = 0;
return 0;
}
int*; *p可以访问4个字节。
char*; *p可以访问1个字节。
double*; *p可以访问8个字节。
原因 是类型本身所需的内存空间就是指针可以控制的空间。
意义:使用时选用合适的指针类型进行定义
2. 指针加减整数
int main(){
int a = 0x11223344;
int* p1 = &a;
char* p2 = &a;
printf("%p\n",p1);
printf("%p\n",p1+1);
printf("%p\n",p2);
printf("%p\n",p2+1);
return 0;
}
int类型时0C->10 变化4, char类型时0C->0D 变化1。
理解:指针加一不是指向下一个紧挨着的地址,是指向下一个指针变量对应的类型变量开始的地址。
意义 指针类型决定了:指针走一步走多远(指针的步长)
野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针的成因
-
指针未初始化
int main(){
int a;
int *p;
}
-
指针越界访问
int main(){
int arr[10];
int *p = arr;
for(int i = 0;i<12;i++){
p++;
}
}
-
指针指向的空间释放
int* test(){
int a = 10;
return &a;
}
int main(){
int *p = test();
return 0;
}
解析:在main函数调用test()时,进入test()函数,int a语句开辟临时的内存空间并将这个内存空间存储为10;返回函数的时候返回的临时的a的地址给*p,然后test函数已经在执行完test函数后结束,a的内存空间被销毁。这时的*p就是指向的地址正确但是内容已经改变。
将未知位置的值进行修改是非常危险的
如何避免野指针
- 指针初始化
- 小心指针越界
- 指针指向内存释放 即 指向NULL
- 指针只用之前检查有效性
指针运算
- 指针加减整数
- 指针-指针
- 指针的关系运算
指针加减指针
int main(){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr)/sizeof(arr[0]);
int* p = arr;
for(int i=0;i<sz;i++){
printf("%d ",*p);
p = p+1;
}
int* p = &arr[9];
for(int i=0;i>0;i++){
printf("%d ",*p);
p-=1;
}
return 0;
}
指针-指针
int main(){
int arr[10]={1,2,3,4,5,6,7,8,9,10};
printf("%d",&arr[9]-&arr[0]);
printf("%d",&arr[0]-&arr[9]);
return 0;
}
指针减指针必须是自己减去自己。否则结果不可预知。
指针实现strlen()
int my_strlen(char* str){
char* start = str;
char* end = str;
while(*end != '\0'){
end++;
}
return ;
}
int main(){
char arr[] = "hello";
int len = my_strlen(arr);
printf("%d\n",len);
return 0;
}
指针的关系运算
int main(){
float values[5];
for(float* vp=&values[5];vp>&values[0];){
printf("haha ");
*--vp = 0;
}
return 0;
}
这里碰到了两个问题 1. values[5]本身不属于数组的部分。但是可以使用。经测试values[5]不会警告,但是values[-1]及以下或values[6]及以上都会报错。2.指针的加减是类型位置的移动数组总也就是一个一个往过走。
for(float* vp=&values[5-1];vp>=&values[0];vp--){
printf("haha ");
*vp = 0;
}
这里在绝大多数的编译器上是可以顺利完成任务的,然而我们应该避免这第二种写法,因为标准不能保证他是可行的。
标准规定:允许指向数组元素的指针和指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个位置的指针进行比较。
指针和数组
int main(){
int arr[10]={0};
printf("%p\n",arr);
printf("%p\n",&arr[0]);
}
一般情况数组名都代表首元素的地址
除了:1. &数组名 这时数组名代表整个数组的地址
? 2. sizeof(数组名) 这时也是代表整个数组。
二级指针
将第一层指针1想成变量,再取这个变量的地址存为一个指针2。那么指针2指向指针1,指针1指向原变量。原变量的地址存在了指针1中,指针1的地址存在了指针2中。
int main(){
int a = 10;
int* pa = &a;
int** ppa = &pa;
}
指针数组、数组指针
指针数组其实是个数组,数组指针是个指针
指针数组:存放指针的数组
int a = 10;
int b = 20;
int c = 30;
int* arr[3] = {&a,&b,&c};
数组指针:指向数组的指针。
main(){ int a = 10; int* pa = &a; int** ppa = &pa;//ppa就是二级指针。 //存在三级及以上指针,(无限套娃) }
### 指针数组、数组指针
指针数组其实是个数组,数组指针是个指针
<u>**指针数组**</u>:存放指针的数组
~~~c
int a = 10;
int b = 20;
int c = 30;
int* arr[3] = {&a,&b,&c};//指针数组
数组指针:指向数组的指针。
|