¶指针
指针,是C语言中的一个重要特性。运用好指针,我们对于内存工作的理解也会有深刻的认识。
¶内存、地址和指针
¶内存
![内存](内存.png “内存”)
如上图所示,这就是内存在计算机中的表现形式,每一个单位称为一个字节,每个字节包含存储一个字符所需要的位数。
![内存单位](内存单位.png “内存单位”)
为了存储更大的值,多个字节组合成一个更大的单位。上图是以4个字节的字来表示。
- 注意:
1.尽管一个字包含4个字节,但它仍然只有一个地址。
2.内存中的每个位置(一个地址)有一个独一无二的标识
3.内存中的每个位置(一个地址)都包含一个值
¶地址
下面一个例子显示了内存中5个字的内容
方框上方是每个字的在内存中的地址,方框中的值就是这个地址中的内容
高级语言的特性就是通过名字来访问内存的位置,高级语言使用名字来代替地址,这些名字就是变量。
指针变量,则是指向这些名字的变量(指向变量的变量),指针变量指向一个内存地址。通过取地址运算符指向名字所代表的地址,通过解引用得到这个内存地址中的内容。
¶指针
¶定义
C语言中的指针是指专门用来存放内存地址的变量,每个指针都有与之相关联的数据类型,该类型决定了指针所指向的变量的类型。
1 | char *p ; //定义了一个char类型的指针变量,这个指针变量能指向一个char类型的内存地址 |
¶指针的运算符
1 | &: 取地址运算符 , 指针通过取地址运算符得到变量名代表的地址 |
¶指针表达式
以一个声明开头,首先声明和初始化需要的变量
1 | char ch = 'd'; |
-
注意
以下图中加粗的椭圆表示表达式的值
方框表示地址
方框上的字符表示内存别名 -
&ch表达式
解释:表达式&ch意思为将变量ch取地址,得到ch的地址,这个地址中的内容就是’d’。当&ch用作右值时,取的是ch的地址;作为左值则是非法(左值一般是变量,用于存储右值,&ch会取得一个地址,不是变量)。 -
cp表达式
解释:表达式cp是一个指针变量(指向一个变量的变量),在声明中已经为cp指向&ch。当作为左值时,它的左值就是cp所处的内存位置;当作为右值就是cp的内容。 -
&cp表达式
解释:表达式&cp是将指针变量cp取地址,得到指针变量的地址。cp是一个指向ch地址的指针变量,&cp作为右值则是一个不知名的指针(指向字符d的指针的指针)指向的地址。同样,不能作为左值。 -
*cp表达式
解释:表达式*cp是将指针cp解引用,得到ch地址中的内容’d’。当作为右值时,它的值是ch中的内容;作为左值是得到它自己的地址,*cp的地址是ch的地址;作为右值则是它自己的内容,它的内容是ch的内容。 -
*cp+1
解释:*cp+1,cp先解引用得到cp指向地址的内容’d’,然后+1,得到一个新值’e’。当作为左值时,表达式是一个具体的值,不能作为左值;作为右值,它的值是一个新值’e’。 -
*(cp+1)
解释:*(cp+1),得到一个新的指针cp+1,指向一个新的地址,然后将指针解引用,得到指向的新地址的内容。作为右值时,表示这个cp+1指向的新的地址的内容;作为左值时,表示cp+1指向的新的地址本身。 -
++cp
解释: ++cp,可以分为两个变量,增值前的指针和增值后的指针。增值前的指针先增加得到一个新的指针指向一个新的地址,增加后的指针得到增加前的指针+1后的一份拷贝,增加后的指针也是指向同一个新地址。 -
cp++
解释: cp++,先返回一份cp的拷贝,然后再增加cp的值,表达式的值则是原来cp的值的一份拷贝。 -
*++cp
解释: 间接访问操作符作用于增值后的指针的拷贝上,所以它的右值是ch后面的那个内存地址的值,左值是是那个位置本身。 -
*cp++
解释: *cp++,右值是变量ch的值,左值是变量ch的内存位置。 -
++*cp
解释: 先执行间接访问操作,然后,cp所指向的的值增加1。
¶指针和数组
C语言中,指针和数组密切相关,数组名实际上是指向数组第一个元素的指针
1 | int a[] = {1,2,3,4,5,6}; //一个数组在内存中表示一个逻辑上连续存储的内存块 |
示例:
1 |
|
- 注意区别:
*p++ : 先取得当前p所指向的变量值再使得p指向后一个变量,相当于a[i++]
*p-- : 先取得当前p所指向的变量值再使得p指向前一个变量,相当于a[i–]
*++p : 先使p指向后一个变量再取得p所指向的变量的值,相当于a[++i]
*–p : 先使p指向前一个变量再取得p所指向的变量的值,相当于a[–i]
¶指针和函数
-
指针作为函数的参数
作用是将一个变量的地址送到一个函数中。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void change(int i , int *p)
{
i++ ;
if(p!=NULL)
{
(*p)++; //先解引用取得当前指向的变量值,再将值+1,然后指向新的值(与*p++有区别)
}
}
int main()
{
int a=5,b=10;
change(&b);
printf("a=%d,b=%d\n",a,b);//a=5,b=11;a传进函数后,成为这个函数的局部变量,在函数销毁后,局部变量得到的值也会被销毁;b是将地址传进函数,通过指针运算,指针p将b的地址中的值+1,最终函数销毁,b的地址没有被销毁,因此b的值变了
return 0 ;
} -
返回指针的函数
作用是返回指针,即地址(指针指向的是地址)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
char *name[3] = {"are","you","ok"};//一个字符串需要用指针来获取
char *message = "wrong input" ;
char *test(int i)
{
if(i<0 || i>3)
{
return message ;//返回一个指向字符串的指针
}
else
{
return name[0] ;//返回数组的首地址
}
}
int a ;
char *p ;
printf("input:");
scanf("%d",&a);
p = test(a); //p获取返回的字符串
printf("%s\n",p); -
指向函数的指针
指向函数的指针是一个指针变量,它 指向一个函数。一个函数的 函数名是一个指针,它指向 函数的代码。函数的调用可以通过函数名来调用也可以通过指向函数的指针调用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//定义一个获取最大值函数
int get_max(int i , int j)
{
return i>j?i:j ;
}
//定义一个获取最小值函数
int get_min(int i , int j)
{
return i>j?j:i ;
}
int compare (int i , int j , int flag)
{
int ret ;
int (*p)(int , int); //定义一个指向有两个int参数的函数的函数指针
if(flag == GET_MAX) //如果为真
{
//笔者注:可以把p看做是引用变量,指向一个函数对象
p = get_max ; //使得函数指针p指向函数get_max的入口地址,让函数指针作为get_max函数的别名
}
else
{
p = get_min ;
}
//笔者注:有点像面对对象的多态,p就是一个接口,而get_max和get_min函数就是这个接口的具体实现,需要用到那个函数就使用p调用哪个函数
ret = p(i,j);//将参数传入函数指针,通过函数指针p来调用p指向的函数,获取返回值
return ret ;
}
int main()
{
int i=5 , j =10 ,ret ;
ret = compare(i,j,GET_MAX);
printf("the MAX is %d\n",ret);
return 0 ;
} -
函数指针做形参
函数的参数可以是指针,那么指向函数的函数指针也可以作为函数参数传递。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int get_big(int i , int j)
{
return i>j?i:j ;
}
//笔者注:其实可以把函数形参中的函数指针放在函数中,作为局部变量
int get_max(int i , int j , int k , int (*p)(int , int))
{
int ret ;
ret = p(i,j);
ret = p(ret , k);
return ret ;
}
//函数指针作为局部变量
int get_max1(int i, int j , int k)
{
int (*p)(int ,int);
int ret ;
p = get_big ;
ret = p(i,j);
ret = p(ret,k);
return ret ;
}
int main()
{
int i=5,j=10,k=15,ret1,ret2 ;
//两个方法都可行
ret1 = get_max(i,j,k,get_big);
ret2 = get_max1(i,j,k);
printf("get_max:%d\nget_max1%d\n",ret1,ret2);
return 0 ;
} -
返回函数指针的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//笔者注:将get_big这个函数看作是商品
int get_big(int i , int j)
{
return i>j?i:j ;
}
//笔者注:可以把这个返回函数指针的函数看作是快递员,商品通过快递员运送
int (*get_function(int a))(int , int)
{
printf("%d\n",a);
return get_big ; //返回一个函数指针,将函数返回
}
int main()
{
int i=5 , j = 10 , max ;
int (*p)(int , int); //定义一个函数指针
//笔者注:最后快递员将商品运送到客户那里,100就是商品的快递号
p = get_function(100); //用函数指针指向返回函数指针的函数的返回值,得到一个函数,现在p是指向get_big函数的函数指针
//笔者注:客户开始使用商品
max = p(i,j); //现在的p调用一个函函数
printf("The MAX is %d\n",max);
return 0 ;
} -
指向字符串的指针
C语言中没有字符串类型的关键字,但是有多种方法访问字符串。- 用字符数组存放字符串
- 用字符串指针来指向字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int main()
{
//采用字符数组拷贝字符串
char a[] = "Linux C Program" , b[20],c[20];
int i ;
//数组采用指针形式
for(i=0 ; *(a+i) != '\0' l i++)
{
*(b+i) = *(a+i);
}
*(b+i) = '\0' ;
//采用字符指针拷贝字符串
char *p1 , *p2 ;
p1 = a ; //p1指向数组a首地址
p2 = c ;
//使用解引用运算符获取指向数组中的值
//对指针变量进行增量加,实现对数组块的地址遍历
for(;*p1 != '\0' ; p1++,p2++)
{
*p2=*p1 ; //将指针p1指向的数组中的元素的值传给指针p2指向的数组的元素
}
*p2='\0';
printf("%s\n",a);
printf("%s\n",b);
printf("%s\n",c);
return 0 ;
}
¶结束语
指针作为C语言的精髓,对于初学者来说确实难学。难学归难学,在真正理解并接受指针这个概念后,你将被指针的绝妙所折服。正所谓,不识庐山真面目,只缘身在此山中。