一、指针的相关概念
1.1、地址与指针与变量
内存区的每一个字节都有一个编号,这就是“地址”。如果在程序中定义了一个变量,在对程序进行编译或运行时,系统就会给这个变量分配内存单元,并确定它的内存地址(编号)。通过访问这个地址,就可以找到所需要的变量,这个变量的地址称为该变量的 指针。指针的实质就是内存“地址”。指针就是地址,地址就是指针。
变量的地址是变量和指针两者之间连接的纽带,如果一个变量包含了另一个变量的地址,则可以理解为第一个变量指向第二个变量。所谓的指向就是通过地址实现的。因为指针变量是指向一个变量的地址,所以将一个变量的地址赋给这个指针变量后,这个指针变量就指向了该变量。
在程序代码中是通过变量名对内存空间单元进行存取操作的,但是代码经过编译后已经将变量名转换为该变量在内存中的存放地址,对变量值的存储都是通过地址进行的。
我们可以使用 &运算符 来获取变量的地址,使用 *运算符 来获取指针类型所指向的值。
1.2、指针变量
1.2.1、什么是指针变量
由于通过地址能访问指定的内存存储单元,可以说地址“指向”该内存单元。地址可以形象地称为指针,意思是通过指针能找到内存单元。一个变量的地址称为该变量的指针。如果有一个专门用来存放另一个变量的地址,它就是 指针变量。在 C语言 中,有专门存放内存单元地址的变量类型,即 指针类型。
1.2.2、指针变量的定义
指针也是一种数据类型,指针变量也是一种变量。所有的指针类型的变量存储的都是内存地址。指针变量指向谁,就把谁的地址赋值给指针变量。“*”操作符操作的是指针变量指向的内存空间。
数据类型 *指针变量名;
其中,“*”表示该变量是一个指针变量,变量名即为定义的指针变量名,数据类型表示本指针变量所指向的变量的数据类型;
int *p;
1.2.3、指针变量的赋值
给指针变量所赋的值只能是变量的地址值。在 C语言 中,提供了地址运算符“&”来表示变量的地址。
指针变量名 = &变量名;
我们可以在定义指针变量的同时进行赋值:
int a = 10;
int *p = &a;
同样,我们也可以先定义指针变量,再进行赋值:
int a = 10;
int *p;
p = &a;
1.2.4、指针变量的引用
引用指针变量是对变量进行间接访问的一种形式,对指针变量的引用形式如下:
*指针变量名;
其含义是引用指针变量所指向的值;
#include <stdio.h>
int main()
{
int a = 10;
// int *代表是一种数据类型,int*指针类型,p才是变量名
// 定义了一个指针类型的变量,可以指向一个int类型变量的地址
int *p;
// 将a的地址赋值给变量p,p也是一个变量,值是一个内存地址编号
p = &a;
printf("a的值为:%d\n",a);
printf("a的地址为:%p\n",&a);
printf("p的地址为:%p\n",p);
// p指向了a的地址,*p就是a的值
printf("p指向的值为:%d\n",*p);
return 0;
}
& 可以取得一个变量在内存中的地址。但是,不能取寄存器变量,因为寄存器变量不在内存里,而在 CPU 里面,所以是没有地址的。
我们可以通过指针间接修改变量的值。
#include <stdio.h>
int main()
{
int a = 10;
int *p = &a;
printf("修改前a的值为:%d\n",a);
*p = 100;
printf("修改后a的值为:%d",a);
return 0;
}
1.3、指针的大小
使用 sizeof() 测量指针的大小,得到的总是:4 或 8。sizeof() 测的是指针变量指向存储地址的大小。在 32位 平台,所有的指针(地址)都是 32位(4字节);在 64位 平台,所有的指针(地址)都是 64位(8字节);
#include <stdio.h>
int main()
{
printf("sizeof(short *): %d\n",sizeof(short *));
printf("sizeof(int *): %d\n",sizeof(int *));
printf("sizeof(long *): %d\n",sizeof(long *));
printf("sizeof(long long*): %d\n",sizeof(long long *));
printf("sizeof(float *): %d\n",sizeof(float *));
printf("sizeof(double *): %d\n",sizeof(double *));
printf("sizeof(long double *): %d\n",sizeof(long double *));
return 0;
}
1.4、指针的算数运算
指针式一个用数值表示的地址,可以对指针进行算数运算:++、--、+、-。指针的算数运算不同于普通变量的算数运算,指针会按照它所指向的数据类型字节长度进行增或减。
#include <stdio.h>
int main()
{
int array[] = {1,2,3,4,5,6,7,8,9};
int *ptr = array;
int i = 0;
for(i = 0; i < sizeof(array)/sizeof(array[0]); i++)
{
printf("%p\n",ptr);
ptr++;
}
return 0;
}
指针的减法就是内存地址的偏移量,即 内存地址的差值/sizeof(数据类型);
#include <stdio.h>
int main()
{
int array[] = {1,2,3,4,5,6,7,8,9};
int * p = &array[3];
int step = p-array;
printf("%d\n",step);
printf("%p\n",p);
printf("%p\n",p-2);
printf("%p\n",&array[1]);
printf("%d\n",*(p-2));
return 0;
}
如果两个指针变量指向的是同一个连续空间的不同存储单元,则这两个指针变量才可以相减,表示两个变量相隔的元素个数。
1.5、指针的比较
指针可以用关系运算符进行比较。如果 p1 和 p2 指向两个变量,比如指向同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较。
#include <stdio.h>
int main()
{
int array[] = {1,2,3,4,5,6,7,8,9};
int *p = array;
if(p == &array[0])
{
printf("%p\n",p);
}
return 0;
}
1.6、野指针和空指针
指针变量也是变量,是变量就可以任意赋值,不要越界即可(32位 为 4字节,64位 为 8字节),但是,任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域)。所以,野指针不会直接引发错误,操作野指针指向的内存区域才会出问题。
#include <stdio.h>
int main()
{
// 野指针 --> 指针变量指向一个未知的空间
// 程序中允许出现野指针
int *p = 100;
// 操作野指针对应的内存空间可能报错
// 操作系统将地址编号0-255作为系统占用,不允许访问操作
printf("%d\n",*p);
return 0;
}
不建议直接将一个变量的值赋值给指针变量,因为这有可能会导致野指针的出现。
但是,野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量(空闲可用),C语言中,可以把 NULL 赋值给此指针,这样就标志此指针为空指针,没有任何指针。
int *p = NULL;
NULL 是一个值为 0 的宏常量:
#define NULL ((void *)0)
操作空指针对应的内存空间一定报错
1.7、万能指针
void * 指针可以指向任意变量的内存空间,它不指向任何的数据类型,在通过万用指针修改变量的值时,需要找到变量对应的指针的类型。
#include <stdio.h>
int main()
{
int a = 10;
char ch = 'a';
// 万能指针可以接收任意类型变量的内存地址
void *p;
printf("万能指针在内存占的字节大小:%d\n",sizeof(void *));
// 在通过万用指针修改变量的值时,需要找到变量对应的指针的类型
p = &a;
*(int *)p = 100;
printf("%d\n",a);
printf("%d\n",*(int *)p);
printf("%p\n",p);
p = &ch;
*(char *)p = 'c';
printf("%c\n",ch);
printf("%c\n",*(int *)p);
printf("%p\n",p);
return 0;
}
1.8、const修饰的指针变量
通过指针,我们可以间接改变用 const 修饰的常量的值;
#include <stdio.h>
int main()
{
const int a = 100;
int b = 200;
// 指针间接修改常量的值
int *p = &a;
*p = 300;
printf("%d\n",a);
return 0;
}
const 修饰指针类型,表示我们可以修改指针变量存储的地址值,但是不能修改指针指向内存空间的值。
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
// const 修饰指针类型
const int *p = &a;
// 可以修改指针变量存储的地址值
p = &b;
// 但是不能修改指针指向内存空间的值
//*p = a;
return 0;
const 修饰指针变量,表示我们可以修改指针指向内存空间的值,但是不能修改指针变量存储的地址值;
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
// const 修饰指针变量
int * const p = &a;
// 可以修改指针指向内存空间的值
*p = a;
// 但是不能修改指针变量存储的地址值
//p = &b;
return 0;
}
const 同时修饰指针类型和指针变量,表示我们既不可以修改指针指向内存空间的值,也可以修改指针变量存储的地址值;
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
// const 修饰指针类型,修饰指针变量
const int * const p = &a;
// 不可以修改指针指向内存空间的值
//*p = a;
// 不可以修改指针变量存储的地址值
//p = &b;
return 0;
}
二、指针与数组
系统需要提供一定量连续的内存来存储数组中的个元素,内存都有地址,指针变量就是存放地址的变量,如果把数组的地址赋给指针变量,就可以通过指针变量来引用数组。
2.1、一维数组与指针
当定义一个一维数组时,系统会在内存中为该数组分配一个存储空间,其数组的名称就是数组在内存中的首地址。若再定义一个指针变量,并将数组的首地址传给指针变量,则该指针就指向了这个一维数组。
int *p;
int array[10];
p = array;
这里 array 是数组名,也就是数组的首地址,将它赋给指针变量 p,也就是将数组 array 的首地址赋给 p。我们也可以写成如下形式:
int *p;
int array[10];
p = &a[0];
上面的语句是将数组 array 中的首个元素的地址赋给指针变量 p。由于 array[0] 的地址就是数组的首地址。
#include <stdio.h>
int main()
{
int array[] = {1,2,3,4,5,6,7,8,9};
// 指向数组的指针
int *p = array;
int i = 0;
printf("%p\n",array);
printf("%p\n",p);
for(i = 0; i < sizeof(array)/sizeof(array[0]); i++)
{
printf("%d ",*(p+i));
}
return 0;
}
通过指针的方式来引用一维数组中的元素
p+i
与array+n
表示数组元素a[n]
的地址,即&a[n]
- 用
*(p+i)
和*(array+n)
来表示数组中的个元素
在 C语言 中可以用 array+n 表示数组的地址,*(array+n) 表示数组元素;指针的移动可以使用“++” 和 “--”这两个运算符。
指针类型+1等同于内存地址+sizeof(数据类型);
2.2、二维数组与指针
#include <stdio.h>
int main()
{
int a[] = {1,2,3};
int b[] = {4,5,6};
int c[] = {7,8,9};
int *array[] = {a,b,c};
int i = 0, j = 0;
for(i = 0; i < 3; i++)
{
for(j = 0; j < 3; j++)
{
//printf("%d ",array[i][j]);
//printf("%d ",*(array[i]+j));
// array是指针数组的首地址
// *(array+i)是指针数组中保存的一维数组的地址值
// *(*(array+i)+j)是指针数组中保存的一维数组中的具体元素
printf("%d ",*(*(array+i)+j));
}
puts("");
}
return 0;
}
通过指针的方式来引用一维数组中的元素。
&array[m][n]
就是第 m 行 n 列元素的地址。&array[0][0] 既可以看作数组 0 行 0 列的首地址,也可以看作二维数组的首地址。array+n
表示第 n 行的首地址。array[0]+n 表示第 0 行 第 n 个元素的地址。*(*(array+n)+m)
与*(a[n]+m)
表示第 n 行 第 m 列元素。
2.3、指针数组
指针数组,它是数组,数组的每个元素都是指针类型。它的定义格式如下:
数据类型 *指针数组名[数组长度];
#include <stdio.h>
int main()
{
int a = 10,b = 20,c = 30;
// 指针数组里面元素存储的是指针
int *array[3] = {&a,&b,&c};
int i = 0;
printf("指针数组大小:%d\n",sizeof(array));
printf("指针元素大小:%d\n",sizeof(array[0]));
for(i = 0; i < sizeof(array)/sizeof(array[0]); i++){
printf("%d\n",*array[i]);
}
return 0;
}
三、指针与字符串
访问一个字符串可以通过两种方式,第一种方式是使用字符数组来存放一个字符串,从而实现对字符串的操作;另一种方式是使用字符指针指向一个字符串,此时可以不定义数组。
#include <stdio.h>
int main()
{
char *str = "hello world!\n";
printf("%s\n",str);
return 0;
}
字符数组是一个一维数组,而字符串数组是以字符串作为数组元素的数组,可以将其看成一个二维数组。
#include <stdio.h>
int main()
{
int i = 0;
char *str[] = {
"Sakura",
"Shana",
"Mikoto"
};
for(i = 0; i < 3; i++)
{
printf("%s\n",str[i]);
}
return 0;
}
四、多级指针
一个指针变量既可以 整型变量、浮点型变量 和 字符类型变量,当然也可以指向指针类型变量。当指针变量用于指向指针类型变量时,则称之为 指向指针的指针变量,也就是 多级指针。指向指针的指针变量定义如下:
数据类型 **指针变量名;
其含义为定义一个指针变量,它指向另一个指针变量,该变量又指向一个基本数据类型。
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int *p = &a;
// pp指二级指针变量的值
// *pp指一级指针的值
// **pp指变量的值
int **pp = &p;
**pp = 100;
printf("%d\n",*p);
printf("%d\n",a);
*pp = &b; // 相当于p = &b;
printf("%d\n",*p);
return 0;
}
整型变量 a 的地址是 &a,将其值传递给指针变量 p,则 p 指向 a;同时,将 p 的地址 &p 传递给 pp,则 pp 指向 p。这里的 pp 就是指向指针变量的指针,即指针的指针。
五、指针与函数
5.1、指针变量作函数参数
C语言 中的实际参数和形式变量之间的数据传递是单向的“值传递”方式。指针变量作为函数参数也是如此,调用函数不可能改变实际参数指针变量的值,但可以改变实际参数指针变量所指向变量的值。
#include <stdio.h>
void swap(int *a,int *b);
int main()
{
int a = 10;
int b = 20;
printf("交换前:a = %d, b = %d\n",a,b);
swap(&a,&b);
printf("交换后:a = %d, b = %d\n",a,b);
return 0;
}
void swap(int *a,int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
5.2、返回指针变量的函数
C语言 允许函数的返回值是一个指针(地址),这样的函数称为 指针函数。定义指针函数的一般格式如下:
返回值类型 *函数名(参数列表);
#include <stdio.h>
#include <string.h>
char *strLong(char *str1,char *str2);
int main()
{
char *str1 = "hello";
char *str2 = "world";
char *result = strLong(str1,str2);
printf("%s\n",result);
return 0;
}
char *strLong(char *str1,char *str2)
{
if(strlen(str1) > strlen(str2))
{
return str1;
} else if(strlen(str1) < strlen(str2))
{
return str2;
} else {
return "st1 is the same length as str2";
}
}
用指针作为函数返回值时,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针尽量不要指向这些数据。这里的销毁并不是将局部数据所占用的内存全部清零,而是程序放弃对它的使用权,后面的代码可以使用这块内存。
C语言 不支持在调用函数时返回局部变量的地址,如果需要可以定义局部变量为 static 变量;
#include <stdio.h>
#include <stdlib.h>
int *fun();
int main()
{
int *p;
int i = 0;
p = fun();
for(i = 0; i < 10; i++)
{
printf("%d ",*(p+i));
}
return 0;
}
int *fun()
{
static int array[10];
int i = 0;
for(i = 0; i < 10; i++)
{
array[i] = rand();
}
return array;
}
5.3、函数指针
一个函数总是占用一段连续的内存空间,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址。把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是 函数指针。函数指针可以简单的理解为指向函数的指针。函数指针的定义格式如下:
数据类型 (*指针名称)(参数列表);
其中,数据类型为函数指针指向的函数返回值类型。参数列表为函数指针指向的函数的参数列表,函数的参数列表可以同时给出参数的类型和名称,也可以只给出参数的类型,省略参数的名称。
#include <stdio.h>
int max(int a, int b);
int main()
{
int num1 = 0,num2 = 0;
int maxNum = 0;
/**
* 定义函数指针
* 函数指针的名字是 pmax
* int 表示该函数指针指向的函数是返回int类型的
* (int,int)表示该函数指针指向的函数形参是接收两个int
* 在定义函数指针是,也可以写上形参名int (*pmax)(int a,int b) = max;
*/
int (*pmax)(int,int) = max;
printf("请输入两个数,中间以空格分隔:\n");
scanf("%d%d",&num1,&num2);
/**
* (*pmax)(num1,num2)通过函数指针去调用函数
* 也可以这样调用pmax(num1,num2);
*/
maxNum = (*pmax)(num1,num2);
printf("the max num is : %d\n",maxNum);
return 0;
}
int max(int a, int b)
{
return a>b ? a : b;
}
5.4、回调函数
函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。简单的来讲,回调函数是由别人的函数执行时调用你传入的函数(通过函数指针完成)。
#include <stdio.h>
#include <stdlib.h>
void initArray(int *array,int arraySiZe,int (*fun)());
int getNextRandomValue();
int main()
{
int array[10];
int i;
/**
* 调用initArray函数
* 传入一个函数名getNextRandomValue,getNextRandomValue是一个地址,需要函数指针接收
*/
initArray(array,10,getNextRandomValue);
for(i = 0; i < 10; i++)
{
printf("%d ",array[i]);
}
return 0;
}
/**
* 回调函数
* fun就是一个函数指针,它可以接收的函数是返回int,没有形参的函数
* fun在这里被initArray调用,充当了回调函数的角色
*/
void initArray(int *array,int arraySiZe,int (*fun)())
{
int i = 0;
for(i = 0; i < arraySiZe; i++)
{
array[i] = fun(); // 通过函数指针调用了getNextRandomValue函数
}
}
int getNextRandomValue()
{
return rand(); // rand()系统函数,会返回一个随机的整数
}
标签:07,int,地址,printf,array,指针,变量
From: https://www.cnblogs.com/kurome/p/17154128.html