指针的概念
- 编号:计算机所有的数据必须放在内存中,不同类型的数据占用的字节数不同,为了正确的访问这些数据,为每个字节都编上号码(每个字节的编号唯一),根据编号可以准确的找到某个字节。(
int 占用4字节, char占用1字节
) 我们将内存中字节的编号成为地址或指针
地址从0开始依次增加。- 如何输出一个地址?
#include <stdio.h>
int main(void){
int a = 100;
char str[20] = "hello world";
printf("%#X, %#X\n", &a, str);
return 0;
}
关于代码的几点说明:(1).%#X : 表示以十六进制形式输出, 并附带前缀 0X
(2)a是一个存放整数的变量,前需要加&来获得他的地址; str本身就表示字符串的首地址,不需要加&。
(在C中,我们有一个控制符%p, 专门用来以十六进制形式输出地址,但输出格式不统一,有的带0x前缀,有的不带);
赠送两张图片:
-
数据和代码都以二进制的形式存储在内存中,计算机无法从格式上区分某块内存到底存储的是数据还是代码。
当程序被加载到内存后,操作系统会给不同的内存块指定不同的权限,拥有读取和执行权限的内存块就是代码,而拥有读取和写入权限(也可能只有读取权限)的内存块就是数据。 -
CPU 只能通过地址来取得内存中的代码和数据(CPU访问时需要的是地址,而非变量名或函数名),程序在执行过程中会告知 CPU 要执行的代码以及要读写的数据的地址。
指针变量的定义和使用
- 数据在内存中的地址也称为指针,如果一个变量存储了一份数据的指针,就称其为指针变量。(指针变量的值就是某份数据的地址)
-
定义:
int a = 100;
int *p_a = &a;
几点说明:(1)两行代码的含义:*表示p_a是一个指针变量, 将变量a的地址赋予它,此时p_a就指向了a。(2) &(取地址符)用来取a的地址 (3)因为a为int型,所以定义指向它的指针变量时,我们用的int定义。可以是float、char等等。 -
定义指针变量时,我们需要带*(表明这个变量为指针变量), 而给指针变量赋值时,不用加
*
。 -
通过指针变量来取得数据:
这里的*
称为指针运算符,用来取得某个地址上的数据。
例如:
#include <stdio.h>
int main(){
int a = 15;
int *p = &a;
printf("%d, %d\n", a, *p); //两种方式都可以输出a的值
return 0;
}
输出为:15,15
- 下面两段代码是等价的,但要注意,第二段中第二行代码不能加*
int *p = &a;
*p = 100;
int *p;
p = &a;
*p = 100;
- 指针不能进行乘除、取余等运算。
可以进行加减、比较等。加减:的结果根数据类型的长度有关。(如int型的指针变量加一,表示指针变量加四)
数组、字符串指针
-
数组是一系列具有相同类型的数据的集合,整个数组占用的是一块内存。
-
定义数组时,数组名可以认为是一个指针,指向数组的第0个元素。(
在C中,我们将第0个元素的地址称为数组的首地址
)我们可以认为数组名就为数组首地址,但两者并不等价。 -
字符数组终究还是一个数组,关于数组指针的规则同样适用于指针数组。另一种表示字符串的方法:(直接适用一个指针指向字符串)
char *str = "http://c.biancheng.net";
char *str;
str = "http://c.biancheng.net";
str指向的是字符串的第0个字符,(我们通常将第0个字符的地址称为字符串的首地址)
两种方式最根本的区别是在内存中的存储区域不一样,字符数组存储在全局数据区或栈区(有读取和写入的权限); 第二种形式的字符串存储在常量区(只有读取权限,没写入权限)。
#include <stdio.h>
int main(){
char *str = "Hello World!";
str = "I love C!"; //正确
str[3] = 'P'; //错误
return 0;
}
/*
第4行代码是正确的,可以更改指针变量本身的指向;第5行代码是错误的,不能修改字符串中的字符。
*/
C指针变量作为函数参数
- 最为经典的就是交换两个变量的值:
#include <stdio.h>
void swap(int a, int b){
int temp; //临时变量
temp = a;
a = b;
b = temp;
}
int main(){
int a = 66, b = 99;
swap(a, b);
printf("a = %d, b = %d\n", a, b);
return 0;
}
#include <stdio.h>
void swap(int *p1, int *p2){
int temp; //临时变量
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
int main(){
int a = 66, b = 99;
swap(&a, &b);
printf("a = %d, b = %d\n", a, b);
return 0;
}
在上述代码段种,第二个swap函数才能真正意义上的交换两数的值。
- 用数组作函数参数:(下面的例子定义了一个函数max,用来查找数组中最大的元素。)
#include <stdio.h>
int max(int *intArr, int len){
int i, maxValue = intArr[0]; //假设第0个元素是最大值
for(i=1; i<len; i++){
if(maxValue < intArr[i]){
maxValue = intArr[i];
}
}
return maxValue;
}
int main(){
int nums[6], i;
int len = sizeof(nums)/sizeof(int);
//读取用户输入的数据并赋值给数组元素
for(i=0; i<len; i++){
scanf("%d", nums+i);
}
printf("Max value is %d!\n", max(nums, len));
return 0;
}
注意:intArr 仅仅是一个数组指针,在函数内部无法通过指针获得数组长度,必须将数组长度作为函数参数传递到函数内部。
用数组做函数参数时,也可满足上述需求。
int max(int intArr[], int len){
int i, maxValue = intArr[0]; //假设第0个元素是最大值
for(i=1; i<len; i++){
if(maxValue < intArr[i]){
maxValue = intArr[i];
}
}
return maxValue;
}
C语言指针作为函数返回值
C语言允许函数的返回值是一个指针(地址),我们将这样的函数称为指针函数。
下面示例中定义一个函数strlong,用来返回两个字符串中较长的一个。(自认为这个函数意义不大,但重在体会返回值和str指针变量)
#include <stdio.h>
#include <string.h>
char *strlong(char *str1, char *str2){
if(strlen(str1) >= strlen(str2)){
return str1;
}else{
return str2;
}
}
int main(){
char str1[30], str2[30], *str;
gets(str1);
gets(str2);
str = strlong(str1, str2);
printf("Longer string: %s\n", str);
return 0;
}
用指针作为函数返回值时需要注意的一点是,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针请尽量不要指向这些数据,C语言没有任何机制来保证这些数据会一直有效,它们在后续使用过程中可能会引发运行时错误。