C语言 指针基础
指针的初始化和定义
指针的作用:
- 有效处理复杂数据
- 方便使用数组和字符串
- 用于函数参数传递,灵活使用函数
- 能像汇编语言一样处理内存地址,使程序精炼高效
变量的地址:
- 内存是以字节为单位进行编址的,内存中的每一个字节都对应一个地址,通过地址才能找到每个字节
- 变量对应内存中的一个存储单元,该存储单元占用一定的字节数,用这存储单元的第一个字节的地址表示变量的地址。
如:一个int类型的存储单元为4个字节,即4个内存单元,取第一个内存单元的地址即首地址作为变量的地址。
指针:
- 变量的地址称为指针
- 指针是一个地址
指针变量:
- 用于存储指针的变量称为指针变量
- 指针变量用于存储变量的地址(即指针)
- 指针变量的值就是变量的地址
指针的定义:
- 类型说明符 *指针变量名;
- 类型说明符表示本指针所指向的变量的数据类型
- "*"为指针运算符
- 根据指向变量的数据类型不同,指针可以是整型指针、字符型指针、浮点型指针、数组指针和其他数据类型指针
指针的初始化:
-
初始化为NULL
指针变量=NULL- NULL为空,值为0,空指针不指向任何变量
- 指针定义后不进行初始化会产生“野指针”。(指针变量会被赋予随机数,指向一个不确定的内存区域)
-
初始化为变量的地址
指针变量=&变量;- &为取地址运算符,用于获取变量的地址
-
定义同时初始化
类型说明符 *指针变量=NULL;类型说明符 *指针变量=&变量;
- 指针变量用来专门存放地址,不能将其他任何非地址类型数据赋予指针变量。一个指针只能指向同一种类型的变量。
int a = 10;
int *p = &a; //定义指针变量p1,指针指向一个整型变量,p1可简称指针
/*指针变量占有的内存空间大小*/
printf("p1=>size:%d\n",sizeof(p1));//p1=>size:4个字节
/*通过变量名直接访问*/
a=20;
printf("a:%d\n",a);
/*通过指针间接访问*/
*p1=30; //左值*p1表示一个存储单元
printf("a:%d\n",*p1);//此处*p1代表右值,代表其指向的单元存储的值
/*输出变量的地址*/
printf("&a:%p,&a:%p\n",&a,p1);//%p代表输出地址或指针
//&a:0xbff8dbcc 地址是一个16位数
p1=(int*)0x12345;//若要更改p1的值,必须强制转换成指针类型,绝对不建议使用
段错误
段错误(Segments fault):发生在编译完程序执行的时候,出现“segment fault”提示,即段错误,很难判断错误在哪里。
段错误产生原因:
- 操作空指针(一定会)
- 操作地址不存在内存
- 操作受系统保护的内存
- 修改常量区中的内容
段错误的常见避免方式:
- 变量、数组、指针等均要做初始化
- 数组不要越界操作
- 通过指针不要随意去操作不存在或受系统保护的内存
段错误的常见解决方式:
- 通过不断注释代码,缩小段错误出现范围
- 利用GDB调试工具查找错误
指针运算
取地址运算符
- &是取地址运算符,为一元运算符,它返回操作数的内存地址。
指针运算符
- *为指针运算符(间接访问运算符),是一元运算符,返回指针指向的存储单元中的值
- 指针运算符是取地址运算符的反运算符
指针赋值运算
- 形式:指针变量1=指针变量2
- 将指针变量2中存放的地址赋值给指针变量1
- 指针赋值实际上是地址的复制
左值和右值
- 左值代表一个存储单元,通常变量表示
- 右值出现在等号右边或输出函数中,代表存储单元中的值
int a=10;
int *p=&a;
*p=*p+1; //相当于*p=a+1
printf("a:%d,a:%d\n",*p,a); //a:11 11
数组名的概念
- 一个数组存储在一块连续内存单元中,数组名就是这块连续内存单元的首地址,也是数组中第一个元素的地址
- 对数组的首地址加上偏移量就可以得到其他元素地址,数组名+1就是偏移一个存储单元
- 数组名是一个常量,不能重新赋值
int a[5]={};
int b[5]={};
a=b; //28号error
指针加减运算
- 指针每递增一次,它都会指向下一个元素的存储单元
- 指针每递减一次,它都会指向前一个元素的存储单元
- 指针的增减时跳跃的字节数将取决于指针所指向变量的数据类型长度
int a[]={1,2,3,4,5};
int *p=a; //指向数组名
p++;
printf("*p:%d\n",*p);
p=a;
p=p+4;
printf("*p:%d\n",*p);
操作 | 意义 |
---|---|
++p_var或p_var++ | 指向var下一个元素 |
- -p_var或p_var- - | 指向var前一个元素 |
p_var+i | 指向var后面第i个元素 |
p_var-i | 指向var前面第i个元素 |
++*p_var或(*p_var)++ | 取出当前var值后var值加1 |
*(p_var++)或*p_var++ | 取出当前var的值后p_var自增 后++优先级高 |
*++p_var 或 *(++p_var) | 将p_var加1,然后输出当前var的值 |
int a[]={1,2,3,4,5};
int *p=a; //指向数组名
printf("++(*p):%d\n",++(*p)); //2 先做数值加1,然后输出
printf("(*p)++:%d\n",(*p)++); //2 输出后,数值加1
printf("a[0]:%d\n",a[0]); //3
//先取元素的值,再指针加1操作
printf("*p++:%d\n",*p++); //3 输出后,指针加1
printf("*(p++):%d\n",*(p++)); //2 输出后,指针加1
printf("*p:%d\n",*p); //3
p=a;
printf("*++p:%d\n",*++p); //2 指针加1,再输出
printf("*(++p):%d\n",*(++p)); //3 指针加1,再输出
指针比较运算
- 前提是两个指针都指向同一个数组中的元素,否则指向两个不同内存区域的指针进行比较没有意义
操作 | 意义 |
---|---|
p_a<p_b | 如果a存储在b的前面则返回true |
p_a<=p_b | 如果a存储在b的前面,或两个指针指向同一位置则返回true |
p_a==p_b | 如果两个指针指向同一位置则返回true |
p_a!=p_b | 如果两个指针指向不同位置则返回true |
p_a==NULL | 如果p_a是空值则返回true |
函数传参
函数传参的方式
-
值传递
- 值传递的数据传送是单向的,即只能把实参的值传给形参,而不能反向传送
- 在函数调用过程中,形参值的变化不会影响到实参值的变化
#include <stdio.h> void swap(int x,int y){ int temp; temp=x;x=y;y=temp; printf("x:%d,y:%d\n",x,y); } int main(void){ int a,b; scanf("%d,%d",&a,&b);//4,5 if(a<b)swap(a,b); printf("a:%d,b:%d\n",a,b); }//输出x:5,y:4,,a:4,b:5
-
地址传递(指针传递)
- 在函数调用过程中形参指针本身值的变化不会影响到实参指针本身值的变化,这个和值传递是相同的。
- 通过形参指针可以修改实参指针所指向的存储单元中的值。
#include <stdio.h> void swap(int *p1,int *p2){ int temp; temp=*p1;*p1=*p2;*p2=temp; } int main(void){ int a,b,*pa,*pb; scanf("%d,%d",&a,&b);//4,5 pa=&a;pb=&b; if(a<b)swap(pa,pb); printf("a:%d,b:%d\n",a,b); }//输出a:5,b:4 /*或者*/ int main(void){ int a,b; scanf("%d,%d",&a,&b);//4,5 if(a<b)swap(&a,&b); printf("a:%d,b:%d\n",a,b); }//输出a:5,b:4
数组指针
指向数组的指针
- 概念:将数组名赋值一个指针,该指针就是指向数组的指针
- 通过指向数组的指针可以操作数组中的元素
数组的遍历方式
- 下标法:引用数组元素可以用下标找到对应的元素并对其进行操作
- 指针法:通过指向数组元素的指针可引用到所需的元素。
- 使用指针法能使程序操作灵活,占内存少,运行速度快,质量高
- 在编译时编译器会将a[i]当成*(a+i)来编译(i为下标变量),因此数组就算越界,语法也不会错,但可能会造成不可预知的严重后果。
- 若int a[5]={1,2,3,4,5}; int *p=a;
则*(p+i)等价于a[i]和*(a+i),i为下标变量
#include <stdio.h>
int main(void){
printf("\003[2J"); //清屏标识符
int a[]={1,2,3,4,5};
int *p=a; //指向数组的指针
printf("a:%p &a[0]:%p p:%p\n",a,&a[0],p);
a[1]=2;
*(a+2)=20;
p[3]=100; //p[3]==*(p+3)
*(p+4)=200;
*(p+5)=500; //越界操作
int i;
for(i=0;i<5;i++)printf("%d",a[i]);//下标法
printf("\n************\n");
for(i=0;i<5;i++)printf("%d",*(a+i));//指针法
printf("\n************\n");
for(i=0;i<5;i++)printf("%d",*(p+i));//指针法
printf("\n************\n");
for(;p<(a+5);p++)printf("%d",*p);//指针法
printf("\n************\n");
for(;p<(a+5);)printf("%d",*p++);//指针法
printf("\n************\n");
}
数组的传参方式
- 数组作为函数参数进行传参的四种方式:
实参 | 形参 |
---|---|
数组名 | 数组名 |
指针 | 数组名 |
数组名 | 指针 |
指针 | 指针 |
- 注意:形参的数组名就是一个指针,存储为4个字节
二级指针
二级指针概念
- 二级指针也称为指针的指针
- 二级指针指向另外一个指针变量
二级指针定义:
类型说明符 *二级指针变量=&一级指针变量;
- 二级指针的指针运算符为**
- 二级指针变量中存放的是一级指针变量的地址
int a=10;
int *p = &a;
int **pp; pp = &p;
printf("p:%p *p:%d\n",p,*p);
printf("p:%p *p:%d %d\n",pp,**pp,*(*pp));
printf("pp==&p:%d\n",pp==&p);
printf("*pp==p:%d *pp==&a:%d\n",*pp==p,*pp==&a);
printf("**pp==*p:%d **pp==a:%d\n",**pp==*p,**pp==a);
printf("*(&p)==p:%d\n",*(&p)==p);
常量指针和指针常量
常量指针
- 通过该指针不可以修改它所指向的存储单元中的值,指向一个常量的指针
- 指针本身(即地址)可以被修改
常量指针的定义
类型说明符 const *指针变量;
- 类型说明符表示指针所指向的存储单元中值的数据类型
- 指针运算符(*)在关键字const后面
int a=3,b=4;
int const *pci=&a;
pci=&b; //常量指针,指针本身可以修改
//*pci=5; 所指存储单元值不可修改,编译会发生只读问题
printf("b:%d *pci:%d\n",b,*pci);
int *p=&b; *p=5; //可以通过另外一个指向的指针修改值
printf("b:%d\n",b);
常量指针使用注意
- 通过其他指针还是可以修改常量指针所指向的存储单元中的值
- 常量指针通常作为函数的形参使用,目的就是在函数中不允许通过该常量指针去修改它所指向的存储单元中的值
void out_salary(float const *p){
//*p=10000.0f;函数内不可以修改
printf("salary:%f\n",*p);
}
float salary = 5000.0f;
out_salary(&salary);
指针常量
- 指针本身(即地址)不可以被修改,指针本身为一个常量
- 通过该指针可以修改它所指向的存储单元中的值
指针常量的定义
类型说明符 * const 指针变量;
- 类型说明符表示指针所指向的存储单元中值的数据类型
- 指针运算符(*)在关键字const前面
int a=3,b=4;
int * const cpi = &b;
//cpi = &a; 地址不可修改,编译会发生只读问题
*cpi = 5;
printf("b:%d *cpi:%d\n",b,*cpi);
//可以通过二级指针来修改指针常量
int c=6;
int **pp = &cpi;
*pp = &c;
printf("*cpi:%d\n",*cpi);
指针常量使用注意
- 通过指向指针常量的二级指针还是可以修改它的值,但在编译时会出现警告
指向常量的指针常量
指向常量的指针常量
- 指针本身(即地址)不可以修改
- 通过该指针不可以修改它所指向的存储单元中的值
定义
类型说明符 const * const 指针变量;
- 类型说明符表示指针所指向的存储单元中值的数据类型
- 指针运算符(*)在两个关键字const中间
int a=3,b=4;
int d=8;
int const * const cpci = &d;
cpci = &a;//会出错
*cpci =5;//会出错
printf("*cpci:%d\n",*cpci);
注意点
- 通过其他指针还是可以修改该指针本身的值,也可以修改该指针所指向的存储单元中的值,但是在编译时会出现警告。