目录
1.指针
在计算机内部,每一个字节单元,都有一个编号,称为地址。
内存的地址称为指针,专门用来存放地址的变量称为指针变量
地址:按字节为单位,进行编号;
指针:内存单元的地址;
指针变量:用来保存(某个变量的)地址的变量;
指针的目标变量:指针指向的变量;
变量是对程序中存储空间的抽象
1)一般形式:
<存储类型> <数据类型> *<指针变量名>
存储类型:指针变量自身的存储类型,默认为自动存储;
数据类型:指针指向的目标的数据类型
(由所指向的元素的数据类型决定);
*
指针变量名:合法的标识符;
指针的数据类型可以看做 存储的地址所指向的元素的数据类型+ *
eg: int a=0;
int *p=&a;
p的数据类型可以看做(int *)
eg:
int *p;//表示一个可以保存整形变量地址的指针
&p --->表示指针变量自身的地址,和目标变量没关系
指针所占字节数和操作系统有关,和目标变量无关;
32位操作系统的指针变量占4字节,64位操作系统的指针变量占8字节
2)&和*
&:取一个变量的地址(在一个变量的基础上取地址)
找到你的地址
*:取一个指针指向的变量的值(在一个地址的基础上取值)
顺着地址过去找你
&和*互为 逆运算
空指针:指针存放零值(缺省属性)
eg: int *p=NULL;
野指针:一个没有指向的指针变量(没有储存地址的指针变量),操作野指针会段错误(访问非法内存)
eg: int *p;
p=100;
3)指针的初始化
<存储类型> <数据类型> *<指针变量名>=<地址量>
eg:
int var=10;
int *p=&var;(赋初值)int var=10;
int *p;
p=&var;(赋值)
2 、指针的运算
(指针的类型一样才进行关系运算或算术运算,类型不同运算无意义)
运算的实质是以地址量的运算
1.指针的关系运算
+ - ++ -- (+= -=)
p+q//没有意义
p-q//表示指针之间相隔元素的个数
p+1
p++ //表示地址值向大的方向偏移一个单位(数据类型)
++p
p--
--p//表示地址值向小的方向偏移一个单位
p-1
实际地址量:
p+n= p保存的实际地址+sizeof(p的数据类型)*n(偏移量)p-n同理
3.指针的赋值运算
1)将一个变量的地址赋值给一个具有相同类型的指针变量
eg: int var =10;
int *p;
p=&var;
指针的强转需要注意精度问题
int类型的转换成char类型的,只会保存低8位(char是一个字节)的值2)将一个指针变量赋值给一个具有相同类型的指针变量
eg: int *p=&var;
int *q;
q=p;//p和q 都保存var的地址
3)将一个数组的首地址赋值给一个具有相同类型的指针变量
eg: int arr[5];
int *p=arr;首地址+下标表示 数组
eg:
arr[i]<==>p[i] <==>*(arr+i)<==>*(p+i)
段错误:使用非法内存,看指针的++,--++的优先级高于&和*
a[]={5,8};
p=&a[1];
y=(*--p)++
//y=5 (*--p)=a[0]=5
//a[0]=6 y=a[0]++;,a[0]要加1
2.指针与二维数组
一维数组不能直接赋多维数组的首地址,需要行指针来赋值(p++无法判断是arr[1]还是arr[0] [1])
arr[i] [j]= * (*(arr+i)+j)
二维数组由多个一维数组组成,每个一维数组都是二维数组的元素,元素存取的是地址
arr[i]=*(arr+i)
数组名+1 移动一汉元素,二维数组名常被成为行地址
arr+1---> arr[1]
arr 第整个数组的首地址
*(arr) 第一个一维数组的首地址
1.行指针(数组指针)
用来存储行地址的指针变量成为行指针;
一般形式:
<存储类型><数据类型> (*<指针变量名>)[表达式]
表达式: 用来表示一行有几个元素,(列数)
行指针变量 +1,移动一行元素;
eg: int (*p)[3]=arr; //类型为:int *[3] 把变量名删除,剩下的就是类型
不能用二级指针指向二维数组,指针的类型不同 ** 与 *[列数]
*( *(p+i)+j)<==> *( *(arr+i)+j)
arr:表示整个数组的首地址
arr[0]:表示第一行的首地址
arr+i: &arr[i]
*(arr+i): arr[i] arr[i]中存储着第i行的首地址也就是&arr[i] [0]
*(arr+i)+j: &arr[i] [j]
(*(arr+i)+j): arr[i] [j]
2字符指针
在程序中,初始化字符指针是把字符串的首地址赋给字符串
把一个字符指针直接初始化为一个字符串常量时,相当于让字符指针指向这个字符串的首地址,但是指针变量不能修改其目标的值;
eg: char *p="hello"; //p指向h
这样赋值之后不能修改*p的值
eg: *p='A';//error
可以改变p的指向
p=&c;
想字符指针赋给一个字符串常量时,指针应该指向一个储存空间
3.指针数组
指针数组:由若干个拥有相同的存储类型,数据类型的指针的集合(用来存储指针的数组)
一般形式:
<存储类型><数据类型> *<指针变量数组名>[表达式]
表达式:表示数组的大小,最多能存放指针变量的个数
数据类型:根据要存储的指针类型决定
eg: int arr[3] [2]={1,2,3,4,5,6};
int *p[3]={arr[0],arr[1],arr[2]};
printf("%d\n", * (*(p+i)+j));
注意&*a,a为值,对值取 * 不对
不能对野指针赋值
char *s; s="girls";√
二级指针与指针数组
char *parr[3]={"hello","world",how};
char **p=parr;
int i;
for(i=0;i<3;i++)
{
puts[parr[i]];
puts(*(p+i);
}
printf("%c\n",* (*(parr+i)+j) );
3.const
1.const 修饰常量吗? 不
2.const 修饰的变量一定不可以改变吗?
const 修饰的局部变量可以通过指针修改目标的值
3.const常量化指针
1)const int *p; *p不可更改,p可以更改
int const *p;(const在*P前)常量
2)int * const p; *p可更改,p不可更改
(给p赋值只能初始化)
3)const int * const p;*p不可更改,p也不可更改
(给p赋值只能初始化)
4.void
void型的指针变量是一种不确定数据类型得到指针变量,他可以通过强制转换让该变量指向任何数据类型的变量或数组。
void 指针需要强制转换后在做运算;
一般形式为:
void *<指针变量名称>;
5.函数
库函数:printf()、scanf()、gets()、getchar()
函数的定义:为了实现某一特定功能的独立模块;
5.1函数的一般形式
<存储类型><数据类型> <函数名>(形式参数说明)
{
函数体;
return 表达式;
}
存储类型默认为extern,方便其他文件调用;🥶🥶🥶🥶🥶
数据类型:与返回值保持相同;如果数据类型为void,不需要返回值,return;return:结束当前模块,作为函数返回值;
形式参数说明(形参):为了告诉函数调用者,在调用函数时,传递的参数的个数及其具体类型;如果形参有多个,以,隔开。形参可以没有,函数不需要传参时函数体:可以为大于等于零条语句;
5.2函数的调用
函数名(实际参数);
eg:
scanf("%d",&var);
gets(buf);//s指向了buf (char *gets(char * s))
......
实际参数(实参):给形式参数赋值,没有形参就没有实参
5.4函数的说明
函数的原型:
<数据类型> <函数名>(形式参数说明);
注意:函数在说明是,形式参数可以只给类型
5.5函数的传参
把实参赋值给形参
形参=实参;函数中处理的是形参
函数内部不要有打印
函数内部的变量是局部变量,如要返回局部变量,需要用static来延长生命周期。
5.5.1值传递(复制传递)
把实参的值赋值给形参
eg:从终端输入两个数x,n,请封装一个函数,求x的n次方
从终端输入两个数,请封装函数,实现两个数的交换
5.5.2地址传递
把实参的地址传递给形参,(形参指向实参)形参可以修改实参的值
通过地址来访问数据,修改数据,而不是修改地址;
可以重用值传递来返回函数的运算结果,
void add(int x,int y,int* sum);
add(10,20,&sum);
请输入一串数据,请封装一个函数,求和
5.5.3全局变量传递
5.6数组作为参数传参
数组的传参需要传递首地址和数组长度(长度在功能函数中无法求取);
复制传递:实际上形参也是指针变量,只是形式不同,实际用法和地址传递一样
eg:
int addFunc(int parr[100])
parr依旧是一个指针,而不是数组名,100没有用,可以不写
sizeof(parr)=8
地址传递:
eg:
int addFunction(int *parr)
5.7指针函数
返回值为一个地址的函数:
一般形式:
<数据类型> * <函数名>(形式参数说明)
{ 函数体; return 表达式; }
eg:封装一个函数来求二维数组的和
二维数组的传参分两种:
普通指针:&arr[m] [n] arr[m] 用*p来接收 *(p+i),i<m * n
数组是线性的
行指针: arr 用(*p)[n]来接收 *( *(p+i)+j);
行指针有限制,数组的定义,列数需要与行指针的列数表达式值一致,arr[m] [n] (*p)[n]
5.8函数指针
指向一个函数,保存函数的地址(入口、节点)
函数指针是用来存放函数的地址,这个地址是一个函数的入口地址,而且是函数调用时使用的的起始地址;
函数指针可以减函数作为参数传递给其他参数调用
一般形式:
<数据类型>(*函数指针名)(形参)
数据类型和形参需要根据指针的目标函数确定
eg:
int add(int,int);
int (*p)(int,int);
p=add;
p(2,3);
5.9函数指针数组
就是可以保存多个函数名(函数指针)的数组
一般形式:
<数据类型>(*函数指针名[大小])(形参)
eg:
int (*parr[2])(int,int)
parr[0]=add;
parr[0](100,20)
5.10回调函数
把一个函数作为形参来传递,再使用这个函数,回调函数声明调用函数时,只声明函数类型,不声明具体的变量
eg:
int test(int x,int y,int (*pfunc)(int,int))
{
return pfunc(x,y);
}
test(25,35,add);(add是自己写的求和的函数)
return pfunc(x,y); 才对,也错了一个能运行,说明有一定的纠错机制,但还是应该正确调用