开始好好学习C语言啦,指针是C语言比较难的地方,但是非常重要,所以单独在此记录一下。有执念的人最可怕,一定要好好学习哇!!!
1、指针是什么?
- 含义:
- 指针其实就是一个十六进制数,他也有自己的内存地址,只不过他存储的数值是所指向变量的内存地址;
- 类型:
- 所有的实际类型所对应的指针的类型都是一样的,都是一个代表内存地址的十六进制数,唯一的区别是其指向的变量或常量的数据类型不同;
- 作用:
- 基本用法:
- int a; int *p=&a ;
- 此时 p=&a(a的内存地址) , *p=a(a的数值);
- 给指针变量赋值时,要将一份数据的地址赋给它,不能直接赋给一个整数,例如int *p = 1000;是没有意义的,使用过程中一般会导致程序崩溃。
2、null指针
- 如果没有地址可以给到指针时,建议置为null,如int *p=null; 其地址为0x0, 但是大多情况下程序不允许服务地址为0的内存,因此如果指针指向0,则表示不指向任何东西;
3、指针的运算
- 指针在递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度,比如 int 就是 4 个字节,char是1个字节。
- ++:如本来是1000 ,然后p++,就变成了1004,再++,就变成了1008
4、数组指针
一维数组指针
- 是一个指向一维数组的指针,一开始的时候是指向数组的第一个元素;
-
int str[]={2,6,8}; int *p=str; 不需要使用取地址符,此时p=str=&str[0], 指向数组的第一个元素;
- p+1,就相当于数组的索引增加了1,此时p指向str[1]的地址;其实str++也是一样的;
- * (p+1),就相当于str[1],即数组的第二个元素的数值;
- 注意:*p+1表示的是将*p这个字符的ASCII码加1。如*p本来等于str[0],即为数组里面的第一个元素2,而2的ASCII码为50,然后50+1=51,那么*p+1的值为3,而不是数组里面的第二个元素6。
二维数组指针
- 是一个指向二维数组的指针,一开始的时候是指向数组的第一行元素;
-
int str[2][3]={{2,6,8},{1,2,3}}; int (*p)[3]=str; 不需要使用取地址符,此时p=str[0]=&str[0], 指向数组的第一行元素;
- p+1,就相当于数组的行标增加了,此时p指向第二行;
- *(p+1),表示指向第二行的第一个元素;
- *(p+1)+1,表示指向第二行的第二个元素;
- *(*(p+1)+1),表示第二行第二个元素的数值,很明显,增加一个 * 表示取地址上的数据;
- 等价关系:
str+i == p+i
str[i] == p[i] == *(str+i) == *(str+i)
str[i][j] == str[i][j] == *(str[i]+j) == *(str[i]+j) == ((str+i)+j) == ((str+i)+j)
- 注意:指针数组和二维数组指针在定义时非常相似,只是括号的位置不同:
int *(p1[5]); //指针数组,可以去掉括号直接写作 int *p1[5];
int (*p2)[5]; //二维数组指针,不能去掉括号
5、指针数组
- 数组里面每个元素都是一个指向int值指针:int *str[10];
- 示例:
const char *names[] = {
"Zara Ali",
"Hina Ali",
};
//等同于:
char str[]={
"Zara Ali",
"Hina Ali",
};
const char*names[4];
*names[0]=&str[0]=names;
names[0]=str[0]=*names;
names[1]=str[1]=*(names+1);
6、二级指针
- int **p;
- 是一个指向指针的指针,简单来说,二级指针的数值就是一级指针的地址,是一个十六进制数,而它的地址则是本身的地址;如图所示:
(图片来源:菜鸟教程)
7、指针作为形参
- 若传进去的是地址,则可以通过改变其指向来改变数值:
void getSeconds(unsigned long *par);
int main ()
{
unsigned long sec;
getSeconds( &sec );//传地址,sec的值会随之变化
/* 输出实际值 */
printf("Number of seconds: %ld\n", sec );//12
return 0;
}
void getSeconds(unsigned long *par)
{
/* 获取当前的秒数 */
*par = 12;
return;
}
- 还可以传入数组:
#include<stdio.h>
double avg1(int *arr,int size);
int main(int argc, char const *argv[])
{
int str[]={1,2,3,4};
double re=avg1(str,4);//传入数组,调用函数
priintf("平均值:%f\n",re);
return 0;
}
double avg1(int *arr,int s){
int i=0;
double avg;
int sum=0;
for ( i = 0; i < s; ++i)
{
sum+=(*arr++);//在这个地方,要注意指针所指向的是什么
//也可以这样写:sum+=arr[i]; *arr就是arr[0],也是str[0]
}
avg=(double)sum/s;
return avg;
}
8、函数指针
- 该函数的名称就是一个指向这个函数的首地址的指针;
- int (*sum)(int a,int b);
- 禁止的操作:
- (1)禁止对指向函数的指针进行自增运算++;
- (2)禁止对函数名赋值,函数名也不能用于进行算术运算。
- 示例:fun1=&fun1=fun1=(&fun1),对于pfun1也是适用的。
(不太明白,需要再多去了解一下)int fun1(int,int);
int fun1(int a, int b){
return a+b;
}
int main(){
int (*pfun1)(int,int);
pfun1=fun1;//这里&fun1和fun1的值和类型都一样,用哪个无所谓
int a=(*pfun1)(5,7); //通过函数指针调用函数。
//也可以这样调用:pfun1(5,7);
}
9、指针函数
- 该函数的返回值是一个指针;返回的可以是变量指针、数组指针、函数指针;
- C 语言不支持在调用函数时返回局部变量的地址,除非定义局部变量为 static 变量。
- int *sum(int a,int b) ;
10、函数指针数组
- 数组里每个元素都是一个函数指针;
- 稍微复杂,结合之前的指针数组来理解会好一些,普通的指针数组里面的每个元素就是一个普通的变量指针,而函数指针数组里面的元素是函数指针,简单的来说,就是指针类型不一样而已;
- 示例:
//现在有四个函数去处理文件,
void open();
void read();
void write();
void close();
typedef void (*PF) ();//定义一个函数指针类型的别名
//把上面的四种操作取地址放进一个数组中
PF file_options[]={
&open,&read,
&write,&close
};
//file_options就是&file_options[0]也就是ip,因为ip指向file_options[0]。
int *ip = file_options
//此时通过指针ip就可以来调用这四个操作了;
//要注意的是:ip[1]的含义就和*(ip+1)一样,即为第二个函数read();
类比一下数组指针去理解:
int ia[4] = {0, 1, 2, 3};
int *ip = ia;//ip=&ia[0],*ip=ip[0]=ia[0],*(ip+1)=ip[1]=ia[1]
11、回调函数
- 某个函数的参数是一个函数指针 ,调用这个函数的时候我们传进去的实参是一个函数;
- 我觉得简单一点来说,就是去通过一个函数去调用另外一个函数;可以使用一个总的函数去管理多个类似的函数,根据你传入的参数不同,从而帮你调用不同的函数。
就好像,找代购买东西一样,你和物品之间,需要有一个联系者。
你想买某样东西,你自己无法或者不想直接去买,那么就可以通过代购来帮你买到这个东西。代购会根据你想买的这个物品的名称,去到对应的地方买这个物品,然后再给到你。
你自己——>主函数,物品名称就是回调函数的名称
代购——>调用主函数的函数(中间函数)`在这里插入代码片`
买物品——>回调函数
#include<stdio.h>
#include<stdlib.h>
//回调函数
int real_back(void)
{
return rand();
}
//用来调用回调函数的函数
int use_back(int *a,int (*fun2) (void) )
{
*a=fun2();//在此调用回调函数
return 0;
}
int main(void)
{
int a;
//此时我们把回调函数real_back作为参数传入use_back函数
use_back( &a, real_back);
printf("%d \n",a);
return 0;
}
13、总结
定 义 |
含 义 |
int *p; |
p 可以指向 int 类型的数据,也可以指向类似 int arr[n] 的数组。 |
int **p; |
p 为二级指针,指向 int * 类型的数据。 |
int *p[n]; |
p 为指针数组。[ ] 的优先级高于 *,所以应该理解为 int *(p[n]); |
int (*p)[n]; |
p 为二维数组指针。 |
int *p(); |
p 是一个函数,它的返回值类型为 int *。 |
int (*p)(int,int); |
p 是一个函数指针,指向原型为 int func(int,int) 的函数。 |
14、 测试题
>指针学的差不多了,下面用几道比较经典的题目来看看掌握的如何吧!
- 1、这是一道关于数组指针的题目,试着写出下面的运行结果是什么:
#include <stdio.h>
int main(){
char str[20] = "c.biancheng.net";
char *s1 = str;
char *s2 = str+2;
char c1 = str[4];
char c2 = *str;
char c3 = *(str+4);
char c4 = *str+2;
char c5 = (str+1)[5];
int num1 = *str+2;
printf(" s1 = %s\n", s1);
printf(" s2 = %s\n", s2);
printf(" c1 = %c\n", c1);
printf(" c2 = %c\n", c2);
printf(" c3 = %c\n", c3);
printf(" c4 = %c\n", c4);
printf(" c5 = %c\n", c5);
printf("num1 = %d\n", num1);
return 0;
}
//运行结果:
s1 = c.biancheng.net
s2 = biancheng.net
c1 = a
c2 = c
c3 = a
c4 = e
c5 = c
num1 = 101
//如果全部正确,那就稍微得意一下下吧,学的还是挺不错的,如果不是,就还需要努努力!
1) str 既是数组名称,也是一个指向字符串的指针;指针可以参加运算,加 1 相当于数组下标加 1。
printf() 输出字符串时,要求给出一个起始地址,并从这个地址开始输出,直到遇见字符串结束标志\0。
s1 为字符串 str 第 0 个字符的地址,s2 为第 2 个字符的地址,所以 printf() 的结果分别为 c.biancheng.net 和 biancheng.net。
(注意:C语言标准规定,当数组名作为数组定义的标识符(也就是定义或声明数组时)、sizeof 或 & 的操作数时,它才表示整个数组本身,在其他的表达式中,数组名会被转换为指向第 0 个元素的指针(地址)。)
2) 指针可以参加运算,str+4 表示第 4 个字符的地址,c3 = *(str+4) 表示第4个字符,即 'a'。
3) 其实,数组元素的访问形式可以看做 address[offset],address 为起始地址,offset 为偏移量:
c1 = str[4]表示以地址 str 为起点,向后偏移4个字符,为 'a';
c5 = (str+1)[5]表示以地址 str+1 为起点,向后偏移5个字符,等价于str[6],为 'c'。
4) 字符与整数运算时,先转换为整数(字符对应的ASCII码)。
num1 与 c4 右边的表达式相同,对于 num1,*str+2 == 'c'+2 == 99+2 == 101,
即 num1 的值为 101,对于 c4,101 对应的字符为 ‘e’,所以 c4 的输出值为 'e'。
#include <stdio.h>
int main(){
char *lines[5] = {
"COSC1283/1284",
"Programming",
"Techniques",
"is",
"great fun"
};
char *str1 = lines[1];
char *str2 = *(lines + 3);
char c1 = *(*(lines + 4) + 6);
char c2 = (*lines + 5)[5];
char c3 = *lines[0] + 2;
printf("str1 = %s\n", str1);
printf("str2 = %s\n", str2);
printf(" c1 = %c\n", c1);
printf(" c2 = %c\n", c2);
printf(" c3 = %c\n", c3);
return 0;
}
//运行结果:
str1 = Programming
str2 = is
c1 = f
c2 = 2
c3 = E
把代码改成下面这种形式,更加容易去理解:
#include <stdio.h>
int main(){
char *string0 = "COSC1283/1284";
char *string1 = "Programming";
char *string2 = "Techniques";
char *string3 = "is";
char *string4 = "great fun";
char *lines[5];
lines[0] = string0;
lines[1] = string1;
lines[2] = string2;
lines[3] = string3;
lines[4] = string4;
char *str1 = lines[1];
char *str2 = *(lines + 3);
char c1 = *(*(lines + 4) + 6);
char c2 = (*lines + 5)[5];
char c3 = *lines[0] + 2;
printf("str1 = %s\n", str1);
printf("str2 = %s\n", str2);
printf(" c1 = %c\n", c1);
printf(" c2 = %c\n", c2);
printf(" c3 = %c\n", c3);
return 0;
}
首先我们要明白,char *lines[5]定义了一个指针数组,它的每个元素的类型都是char 。
在表达式中使用 lines 时,它会转换为一个类型为char **的指针,这样*lines就表示一个指向字符的指针,而**lines表示一个具体的字符,这一点很重要,一定要好好理解!
上面的题目中:
1)、lines[1]:它是一个指针,指向字符串string1,即 string1 的首地址。
*(lines + 3):lines + 3 为数组中第 3 个元素的地址,*(lines + 3) 为第 3 个元素的值,它是一个指针,指向字符串 string3。
2)、 *(*(lines + 4) + 6):*(lines + 4) + 6 == lines[4] + 6 == string4 + 6,
表示字符串 string4 中第 6 个字符的地址,即 f 的地址,所以 *(*(lines + 4) + 6) 就表示字符 f。
3)、(*lines + 5)[5]:*lines + 5 为字符串 string0 中第 5 个字符的地址,即 2 的地址,
(*lines + 5)[5]等价于*(*lines + 5 + 5),表示第10个字符,即 2。
4)、*lines[0] + 2:lines[0] 为字符串 string0 中第 0 个字符的地址,即 C 的地址;
*lines[0]也就表示第 0 个字符,即字符 C。字符与整数运算,首先转换为该字符对应的 ASCII 码,
然后再运算,所以 *lines[0] + 2 = 67 + 2 = 69,69 对应的字符为 E。