C语言指针总结
一:什么是指针
内存和地址
我们知道计算机上CPU在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,电脑上的内存是8GB/16GB/32GB等,那这些内存空间是如何高效的管理的呢?
先来看一下计算机中常见的单位:
1 bit - 比特位
2 Byte -字节—1Byte=8bit
3 KB ------------1KB=1024Byte
4 MB -----------1MB=1024KB
5 GB------------1GB=1024MB
6 TB------------1TB=1024GB
7 PB------------1PB=1024TB
其实可以这样理解把一个字节看做一个学生宿舍,而一个字节存放8个比特位可以看做,一个学生宿舍住了8个学生,每一个人就是一个比特位。
每个内存单元都有编号,而编号就叫做地址。
我们可以理解为内存单元的编号=地址=指针;
指针就是地址。
指针变量和地址
1.取地址&
#include<stdio.h> int main(){ int a=10; &a; printf("%p\n",&a); return 0; }
如此我们可以取出a的地址。
2.指针变量和解引用操作符----*
我们在通过取地址操作符&拿到一个地址是一个数值,这个数值也需要存储起来,我们把这样的地址存放到指针变量中。
> #include<stdio.h>
> int main(){
> int a=10;
> int * pa =&a;//取出a的地址存放到指针变量pa中去
>return 0;
> }
解引用操作符()
我们有了地址(指针),我们想访问指针所指向的对象时就要通过解引用操作符来访问。
> #include<stdio.h>
> int main(){
> int a=100;
> int * pa=&a;
> *pa=20;
> return 0;
> }
通过上面的代码我们把a的值由原来的100改为了20。
指针变量的大小
我们知道变量都有大小比如:int占4个字节,char占1个字节,float占4个字节等等。那指针变量的大小是多少呢?
我们知道32位机器有32位地址线,一个地址线代表了一个bit位,那32个bit就是4个字节。
同理64位机器有64位地址线,那就是8个字节。
一句话:指针变量的大小(地址的大小)是4或8.
3.指针变量类型的意义
先来看一下,下面两段代码:
> #include<stdio.h>
> int main(){
> int n = 0x11223344;
> int*pi=&n;
> *pi=0;
> printf("%d\n",n);
> return 0;
> }
> #include<stdio.h>
> int main(){
> int n = 0x11223344;
> int * pi=(char * )&n;
> *pi=0;
> printf("%d\n",n);
> return 0;
> }
两个代码输出的结果是什么?
第一个输出的n的值为0;
第二个输出的n的值为0x00223344;
> #include <stdio.h>
> int main() {
> int n = 10;
> char *pc = (char*)&n;
> int *pi = &n;
> printf("%p\n", &n);
> printf("%p\n", pc);
> printf("%p\n", pc+1);
> printf("%p\n", pi);
> printf("%p\n", pi+1);
> return 0;
> }
运行结果如下:
从中可以看出char类型的指针变量+1跳过1个字节,int类型的指针变量+1跳过了4个字节。
结论:指针的类型决定了指针向前或者向后走一步有多大(距离)。
4void*指针
void指针是一种特殊的指针,它可以理解为无具体类型的指针,这种类型的指针可以用来接收任意类型的指针。但也有局限,void指针不能进行±运算和解引用操作。
要是想要void*指针能进行运算该怎么办?
通过强转:
eg:
int a=10;
void * pa = &a;
*(int *)pa=5;
这时a的值改成了5.
5.const修饰指针
凡是被const修饰的变量都不可修改。
>#include<stdio.h>
> int main()
>{
>const int n = 0;
>printf("n = %d\n", n);
>int*p = &n;
>*p = 20;
>printf("n = %d\n", n);
>return 0;
>}
n已经被const修饰了所以n的值不可以改变。
const是如何修饰指针变量的呢?
我们来看这三个形式看看它们一样吗?
(1)const int * p;
(2)int const * p;
(3)int * const p;
这三个一样吗?
通过观察看到(1)和(2)const都放到了“ * "的左边(3)放到了 " * " 的右边
所以(1)和(2)是一样的
那(1)和(2)是干嘛的呢?首先const修饰了*p;在左边那就是p解引用的值不可以修改—而(3)const修饰的是p,则是p指向的地址不可以被修改。
> (1)和(2)
> void test(){
> int a = 5;
> const int * p =&a;
> *p = 3 ;//不可以修改
> p = &m;//可以修改
> }
> (3)
> void test(){
> int c = 9 ;
> int * const p = &c;
> *p = 6;//可以修改
> p = & s;//不可以修改
> }
6.野指针
野指针是指指针指向的位置是不可知的,如:随机的,不正确的,没有明确限制的指针。
野指针成因:
(1)指针未初始化
(2)指针访问越界
(3)指正指向的空间释放
7.assert断言
assert.h头文件定义了宏assert()用于在运行时确保程序符合指定的条件,符合则运行,不符合则报错。
8.传值和传址调用
写一个函数交换两个变量的值
先看一下传值
> #include<stdio.h>
> void test(int x , int y){
> int tmp =x ;
> x = y;
> y = tmp ;
> }
> int main(){
> int a = 0;
> int b = 20;
> test( a,b );
> printf("%d %d\n",a,b);
> return 0;
> }
运行之后会发现值并没有交换
这是为啥呢?
在函数创建过程中栈区位函数开辟了一块空间,x和y作为a和b的临时拷贝,在栈区内进行了交换,但是当函数调用完之后,函数的栈帧空间将要销毁,两个变量并没有交换。
来看一下传址调用
>
> #include<stdio.h>
> void test2(int * x , int * y){
> int tmp = *x;
> * x = * y ;
> * y= tmp;
> }
>
> int mian(){
> int a = 10;
> int b = 20;
> test2( &a , &b );
> printf("%d %d\n", a, b);
> return 0;
> }
这时候就两个变量就可以交换了
x指向的是a,y指向的是b,直接对a和b进行操作。
二:数组指针和函数指针
指针数组
我们先来看一下数组名的理解吧。
int arr[ ]={ 1,2,3,5,6,8,7,4,9,10 }
对于数组来说数组名 arr实际上是数组首元素的地址即
arr=&arr[0];
但任何情况下都是吗?
有两种情况不是
(1)sizeof(arr);//这个计算的是整个数组的大小,不是首元素地址的大小,结果是40.
此外看一下这些:
-------sizeof(arr[0]);//这个是首元素的大小,结果是4;
-------sizeof(arr+1);//首先sizeof()中不是单独的arr那arr代表的就不是整个数组,arr代表首元素的地址,而首元素地址加一代表了第二个元素的地址,而地址的大小不是4就是8,所以结果是4或8.
-------sizeof(&arr[3])//arr[3]代表第4个元素而&代表第四个元素的地址,而地址大小就是4或8;(2)&arr
arr代表了首元素的地址而&arr呢?
&arr代表了整个数组的地址,整个数组的地址!!!
整个数组的地址和首元素的地址是相同的,但所占字节数不同,
arr+1是跳过了4个字节
&arr+1是跳过了整个数组,一共跳了40个字节;
------sizeof(&arr)//这个结果又是什么呢?
&arr代表了整个数组的地址,是地址那就是4或者8.
------szieof(&*arr)//这个结果又是什么呢?
我们这样理解&和 * 相互抵消了这样就只有sizeof(arr)结果就是40
现在是不是对数组名的理解更深刻了?
那什么事指针数组呢?
指针数组是数组还是指针呢?
指针数组是存放指针的数组!!!不是指针;
这个要和我们下面介绍的数组指针要区别,数组指针是指针,指针数组是数组!!
接下来我们用指针数组模拟一下二维数组
> #include<stdio.h>
> int mian(){
> int arr1[ ]={ 1,2,3,4,5 };
> int arr2[ ]={ 5,4,3,2,1};
> int arr3[ ]={9,8,7,5,6};
> int* arr[ ]={arr1,arr1,arr3};
> for(int i=0;i<3;i++){
> for(int j=0;j<5;j++){
> printf("%d ",arr[ i ][ j ] );
> }
> printf("\n");
> }
> return 0;
> }
把arr1,2,3放到arr指针数组中模拟了二维数组,当i=0是arr[ i ][ j ]此时是arr[0][ j ];
这是指针指向了第一个元素arr1当j++时访问arr1中的数字
数组指针
上面我们了解到什么事指针数组,那数组指针又是什么?
数组指针是指向整个数组的指针。
数组指针变量又是什么?
我们知道:
整形指针变量是int*
字符指针变量是char*
那数组指针变量呢?
数组指针变量:
int ( *p )[10] ;
解释:p和*先结合,说明p是一个指针变量,然后指针指向了一个大小为10的整形数组。所以p是一个指针,指向一个数组,叫数组指针。
数组指针可以应用于:
二维数组传参
#include <stdio.h>
void test(int (*p)[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 ", *(*(p+i)+j));
}
printf("\n");
}
}
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;
}
二维数组传参test(arr, 3,5)其中数组名是首元素的地址那二维数组的首元素是什么?
二维数组的首元素就是第一行的地址就是arr[ 0 ]的地址
arr+1代表的是第二行的地址。
传arr的地址需要用数组指针去接收,参数的类型就是int(*)[5];
函数指针
函数也有指针
回调函数
回调函数就是⼀个通过函数指针调⽤的函数。
如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数时,被调⽤的函数就是回调函数。回调函数不是由该函数的实现⽅直接调⽤,⽽是在特定的事件或条件发⽣时由另外的⼀⽅调⽤的,⽤于对该事件或条件进⾏响应。
这是一个计算器能进行ADD,SUB,MUL,DIV运算,其中运用了回调函数
当输入1时调用calc()函数而这个函数把函数指针作为了参数就可以继续调用回调函数ADD,SUB,MUL,DIV被调用的叫回调函数
函数指针数组
学习了指针数组,那函数指针也能放到一个数组里吗?
可以的!!!
比如:
int (p[10] )(int ,int );
三:常量字符串
在指针的类型中我们知道有⼀种指针类型为字符指针 char* ;
⼀般使⽤:
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
还有⼀种使⽤⽅式如下:
int main()
{
const char* pstr = "zhangimisinx";//这⾥是把⼀个字符串放到pstr指针变量⾥了吗?
printf("%s\n", pstr);
return 0;
}
这里pstr指向的是第一个字符的地址即Z的地址,且不能被修改;
来看一下这段代码:
#include <stdio.h>
int main()
{
char str1[] = "zhangimisinx";
char str2[] = "zhangimisinx";
const char *str3 = "zhangimisinx";
const char *str4 = "zhangimisinx";
if(str1 ==str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if(str3 ==str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
输出的结果是什么?
str1 and str2 are not same;
str3 and str4 are same;
为什么是这样的结果呢?
对于str1和str2来说都是数组名,而数组名代表的是数组首元素的地址,而两个数组内存中开辟了两个空间,首元素地址当然不同了。
对于str3和str4来说是常量字符串,计算机对于两个一模一样的常量字符串不会再另外开辟空间即两个都指向的是第一个字符Z。