指针和指针变量
地址和指针的概念
在计算机中,内存是一个连续的存储空间。在这个空间中,每一个内存单元都对应一个唯一的内存地址,内粗你的编址由小到大连续排列,它的基本单位为“字节”。在C语言环境下,一个整型变量占4个字节的内存单元,那就要为每一个整 型变量分配4个连续的内存单元, 并且这4个连续存储单元的起始地址就是该变量的地址。所以,编译后每一个变量都对应一个地址,对变量的访问就是通过这个变量的地址进行的。当给一个变量赋值时,实际上是将这个值按该变量的类型存入该变量名对应地址开始的若干连续单元中,变量地址中的内容即是该变量的值。 当引用变量时,就是从该变量名所对应地址开始的若干连续单元中来提取数据。可以通过地址运算符&得到变量的地址。
将内存单元中的地址称为“指针”,地址即为指针。
使用一种类型的变量来存放内存地址,这种变量称为“指针变量”。
指针变量的定义
类型标识符 *指针变量名
例如:
int *p;
“类型标识符”表示该指针所指变量应该具有的数据类型。
指针变量的初始化和引用
基本形式:
类型标识符 *指针变量名;
指针变量名=地址值;
例如:
int i=10;
int *p;
//建立关联,将i的变量地址指向指针变量,如果想取i的值,则通过*p来得到,此时*p=i=10
p=&i;
C语言中指针变量定义时出现的 * 和指针变量引用中出现的 * 其含义不同,定义时的 * 理解为该变量为指针类型变量,即表示 * 后的变量是一个指针变量;引用时 * 为取值运算符,即通过*来对指针变量进行“间接访问”。
实例:
#include <stdio.h>
int main(){
printf("求两个数的和:===============\n");
int i,j,sum;
int *p,*q;
//建立关联,将i,j的变量地址指向指针变量
p=&i;
q=&j;
scanf("%d,%d",p,q);
//通过指针变量得到变量的真实值数值
sum=*p+*q;
printf("%d,%d\n",*p,*q);
printf("i+j=%d\n",sum);
return 0;
}
指针变量的运算
指针变量的运输包括指针变量的赋值运算、算数运算、关系运算等。
- 指针变量的赋值运算
给指针变量赋值只能是地址常量或地址变量,常见为指针变量赋值的几种形式如下。
(1) 将一个变量的地址赋给指针变量。如:
int a,*p;
P=&a;
(2)将一个指针变量的值赋给另外一个指针变量。如:
double x, y,*p, *q;
p=&x;
q=p;
(3)将数组的起始地址赋给指针变量。如:
char a[5],*p;
p=a;
(4)将字符串的起始地址赋给指针变量。如:
char *p;
p=" abcdefgh";
或
char *p="abcdefgh";
- 指针变量与整数的加减运算
在C语言中,地址是可以做算术运算的, 一个地址加上或减去一个整数n,得到一个新的地址,新地址是以该地址为基准点,发生n个单位的地址位移。地址与整数的加减运算也称为“地址位移”,地址位移适用于数组,因为数组元素在内存中的存储地址是连续的。例如:
int a[10];
int *p;
p=a;
p=p+4;
指针p从数组首地址&a[0]移到&a[4]移动4个单位,实际移动了4X4个字节数。
- 指针变量的关系运算
两个指针变量(必须指向相同类型的变量)之间的关系运算,表示它们指向的变量其地址在内存中的位置关系,即存放地址值大的指针变量大于存放地址值小的指针变量。可以使用>、<、>=、<=、=、! =六种关系运算。其中,>、<、>=、<=用于比较两指针变量所指向地址的大小关系;==、! =用于判断两指针变量是否指向同一地址。
例:比较pl与p2的大小。
#include <stdio.h>
int main()
{
int a[10]={10,20,30,40,550,60,70,80,90,100};
/* 指针变量初始化 */
int *p1=&a[0], *p2=&a[5];
printf("%d\n",pl<p2);
return 0;
}
运行结果为 1;
运行结果为1,表面表达式“pl<p2”的值为真;
指针和数组
指针和一维数组
一维数组在内存中的存储是由一段连续的内存单元组成的,数组名为该段连续内存单元的首地址。可以通过数组名(即数组元素首地址)加上相对于首地址的相对位移量来访问每个数组元素的地址,然后来获取每个元素的值。数组名代表该数组的首元素地址。 因此,数组名为指向该数组首元素的指针常量,其值不能改变。例如:
int a[10],*p;
p=&a[0] ;
则有
p=a;
在a数组中,a[i]代表a 数组中第i+1个元素(下标从0开始)。p[i]与a[i]相同,也代表着a数组的第i+1个元素。由于数组名a代表数组首元素的地址&a[0], a+1代表&a[1], a+i 代表&a[i],同样p+i也代表&a[i],则有p+i、a+i 均表示数组元素a[i]的地址。引用一维数组的元素可以采用以下方式。
(1)下标法:采用a[i]或p[i]来访问a数组的第i+1个元素。
假设a数组有10 个元素,通过数组下标实现对a数组各元素进行赋值,并输出所有元素。
#include <stdio.h>
int main(){
printf("\n下标法:采用a[i]或b[i]来访问a数组的第i+1个元素=========\n");
int d[3];
for (int k = 0; k < 3; ++k) {
scanf("%d",&d[k]);
}
for (int l = 0; l < 3; ++l) {
printf("%5d",d[l]);
}
return 0;
}
指针法:采用指针p来指向数组的首地址a的形式来访问a数组的所有元素。
#include <stdio.h>
int main(){
printf("\n指针法:采用指针p来指向数组的首地址a的形式来访问a数组的所有元素=========\n");
int e[3],*pInt;
pInt=e;
for (int m = 0; m < 3; ++m) {
scanf("%d",pInt++);
}
for (pInt=e;pInt<(e+3);pInt++) {
printf("%5d",*pInt);
}
return 0;
}
指针地址位移法:采用*(a+i)或者* (p+i)的形式来访问a数组的第i+1个元素。
#include <stdio.h>
int main(){
int f[3];
for (int n = 0; n < 3; ++n) {
scanf("%d",f+n);
}
for (int l = 0; l < 3; ++l) {
printf("%5d",*(f+l));
}
return 0;
}
指针和二维数组
在一维数组中a[i]与*(a+i)等价,都代表一维数组a中的第i+1个元素。而在二维数组中,a[i]不再是数组元素而表示一个地址,同样*(a+i)也表示 一个地址, 都代表二维数组中第i行的首地址,并不是二维数组某行某列的具体元素。
因此,在二维数组a中,数组元素a[i][j]的地址可用以下几种方式表示:
(1) &a[i][j] /* 行下标和列下标表示法 * /
(2) a[i]+j / * 行下标+列位移表示法 */
(3) * (a+i)+j / * 行位移+列位移表示法 * /
那么,相对应的二维数组的数组元素a[i][j]也有如下的几种表示方法:
(1) a[i][j] / * 行下标和列下标表示法 */
(2) *(a[i]+j) / *行下标+列位移表示法 * /
(3) *( *(a+i)+j) / * 行位移加列位移表示法 */
a[0]为首列(第0列)的首地址,a[0]+1为第1列的首地址,a[0]+2为第2列的首地址,a[0]+i为i列的首地址。
例子:
#include <stdio.h>
int main() {
int a[3][4];
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 4; ++j) {
//利用下标法(行下标和列下标表示法 &a[i][j])实现数组的输入输出
// scanf("%d",&a[i][j]);
//利用行下标加列位移(a[i]+j)实现数组的输入输出
// scanf("%d",a[i]+j);
//利用行位移加列位移(*(a+i)+j)实现数组的输入输出
scanf("%d", *(a+i) + j);
}
}
for (int k = 0; k < 3; ++k) {
for (int i = 0; i < 4; ++i) {
//利用下标法(行下标和列下标表示法 &a[i][j])实现数组的输入输出
// printf("%5d",a[k][i]);
//利用行下标加列位移(a[i]+j)实现数组的输入输出
// printf("%5d", *(a[k] + i));
//利用行位移加列位移(*(a+i)+j)实现数组的输入输出
printf("%5d", *(*(a+k) + i));
}
printf("\n");
}
return 0;
}
指针数组
如果一个数组中的每个元素均为指针类型变量(即由指针变量构成的数组),这样的数组称为指针数组。指针数组定义的一般形式:
类型标识符 *数组名[数组长度]
例如:
int (*p)[5];
例子:
#include <stdio.h>
int main(){
char *week[]={"eere","dfs","Sdfdds","dfsdd","sdfsdf"};
for (int l = 0; l < 5; ++l) {
printf("%-10s",week[l]);
}
return 0;
}
指针数组中每一个元素实际上都是指向另一个数据的指针。因此,可以通过将不同长度的字符串首地址分别放入指针数组的每一个元素中,实现对这些字符串的处理。
指针和字符串
指向字符串的指针就是字符串的起始地址,当把这个地址赋给一个字符指针变量时,就可以很方便地实现对字符串的处理。指向字符串的指针变量的定义的一般形式:
char *变量名
例如:
char *p;
访问字符串的两种方式
- 使用字符数组存放一个字符串,然后输出该字符串。
#include <stdio.h>
int main(){
char a[]="Hello World!";
printf("%s\n",a);
return 0;
}
- 使用字符指针指向一个字符串。
#include <stdio.h>
int main(){
char *a="Hello World!";
printf("%s\n",a);
return 0;
}
使用字符数组和字符指针变量访问字符串的区别
- 字符数组由若干个元素组成,每个元素中存放一个字符,二字符指针变量中存放的是地址。
- 定义字符数组之后,只能对各个数组元素进行赋值,不能用下面的方法对字符数组赋值:
char str[60];
str="Ihave a book!";
而对指针变量,可以采用下面的方法赋值:
char *a;
a="Ihave a book";
赋给a的不是字符,而是字符串的首地址。
(3)对字符指针变量赋初值:
char *a="I have a book";
等价于:
char *a;
a="I have a book";
对数组初始化,只能是:
char str[]="I have a book";
- 如果定义一个字符数组c[10],它有确定的地址,在编译时为它分配10个字节的连续内存单元。
如果定义一个字符指针变量,则给指针变量分配4个字节的内存单元,其中存放一个字符变量的地址。
-
指针变量的值可以改变,而数组名虽代表地址,但它是常量,它的值不可以改变。
-
用指针变量指向一个格式字符串,可以用它代替printf()函数中的格式字符串。
char * format;
format="a=&d,b=gf\n";
printf (format,a,b); /* 等价于printf ("a=8d,b=d\n",a,b); */
例子:将字符串a 复制到字符数组b中。
#include <stdio.h>
int main(){
char a[]="Hello World!",b[20];
for (int i = 0; *(a + i) != '\0'; ++i) {
*(b+i)=*(a+i);
}
printf("输出a数组:%s\n",a);
printf("输出b数组:%s\n",b);
printf("输出b数组:");
for (int j = 0; b[j] !='\0' ; ++j) {
printf("%c",b[j]);
}
printf("\n使用指针变量来处理字符串:----------------------\n");
char *p1,*p2;
p1=a;
p2=b;
printf("输出p1数组:%c\n",*p1);
for (; *p1!='\0'; p1++,p2++) {
*p2=*p1;
}
printf("输出a11数组:%s\n",a);
printf("输出b11数组:%s\n",b);
printf("输出b11数组:");
for (int j = 0; b[j] !='\0' ; ++j) {
printf("%c",b[j]);
}
return 0;
}
指针与函数
指针变量作为函数的参数
- 指向简单变量的指针变量作为函数的参数
#include <stdio.h>
int swap(int *a, int *b);
int main(){
int m = 10, n = 20;
swap(&m, &n);
printf("main:m=%d n=%d\n", m, n);
return 0;
}
int swap(int *a, int *b) {
printf("swap:a=%d b=%d\n", *a, *b);
return 0;
}
- 指向数组的指针变量作为函数的参数
前面已经介绍过,实参数组名代表该数组元素的首地址,而形参是用来接收实参传递过来的数组元素的首地址, 因此,形参应该是个指针变量(只有指针变量才能存放地址)。 实际上,C编译系统都是将形多数组名作为指针变量来处理的。
在实际的使用过程中,数组和指针联系在起有4种使用情况。
(1)形参和实参都用数组。
形参数组名接收实参数组首元素的地址,可以认为形参 数组与实参数组共用段内存单元。
(2)实参用数组名,形参用指针。
实参a为数组名,形参x为指针变量,函数执行时,x 指向a[0],通过x值的改变,可以指向数组a的任一元素。
(3)实参形参都用指针。
实参p和形参x都是指针变量,先使实参指针变量p指向数组a, p的值是&a[0]。然后将p的值传给形参x,x的初始值也是&a[0],通过x值的改变可以使x指向数组a的任一元素。
(4)实参用指针,形参用数组名。
实参p为指针变量,它指向a[0],形参为数组名x(实际是将x作为指针变量处理),将a[0]的地址传给形参X,使指针变量x指向a[0], 可以认为形参数组和实参数组共用存储单元。
例子:用选择发对10个整数按从小到大的顺序排序(实参和形参都用指针)。
#include <stdio.h>
int sort(int x[], int n);
int main(){
int *p, a[4] = {1, 4, 3, 7};
//定义指针变量p,使p指向数组a的第一个元素
p = a;
sort(p, 4);
//输出元素a中的所有元素
for (int j = 0; j < 4; ++j) {
printf("%4d", *(p + j));
}
printf("\n");
return 0;
}
//使用选择排序对数组中的元素进行排序
int sort(int *x, int n) {
int t;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (*(x + j) < *(x + i)) {
t = *(x + i);
*(x + i) = *(x + j);
*(x + j) = t;
}
}
}
return 0;
}
函数返回值为指针
例子:
#include <stdio.h>
#include <stdlib.h>
char *Dayname(int n);
int main() {
printf("\n指针型函数===================================\n");
int t;
scanf("%d", &t);
if (t < 0) {
// exit(1) 表示发生错误后退出程序,exit(0)表示正常退出;
exit(1);
}
printf("day n0 :%d is %s\n", t, Dayname(t));
return 0;
}
//函数的返回值为指针
char *Dayname(int n) {
char *name[] = {"Ill", "M", "W", "T"};
return (n < 1 || n > 7) ? name[0] : name[n];
}
指向函数的指针
一般形式:
类型标识符 (*指针变量名)()
例子:
#include <stdio.h>
int max(int x, int y);
int main() {
printf("\n指向函数的指针变量===================================\n");
//指向函数的指针 形式:类型标识符 (*指针变量名)();
int (*mp)();
int q, w, e;
// 作用是将函数max的入口地址赋值给指针变量,函数名就是函数所占内存区域的首地址,也称为入口地址
mp = max;
scanf("%d%d", &q, &w);
//通过指针函数的指针变量调用函数的语法格式:(*指针变量名)(实参列表);
e = (*mp)(q, w);
printf("a=%d,b=%d,max=%d", q, w, e);
return 0;
}
int max(int x, int y) {
return (x > y) ? x : y;
}
指向指针的指针
使用指针来指向另一个指针数据的指针变量,称为指向指针的指针,一般形式:
类型标识符 **指针变量名
例如:
char **p;
**p相当于 *(*p), *( p )是字符型指针变量, *(*p)是指向字符型指针的指针。
例子:
#include <stdio.h>
int main() {
printf("\n指向指针的指针===================================\n");
char *name[] = {"One", "Two", "Three"};
//存放具体的字符对象
char **u;
for (int i = 2; i >= 0; --i) {
// 存放的是name数组元素的地址
u = name + i;
// 存放的是目标对象的地址
printf("%s\n", *u);
}
return 0;
}
**u表示一个具体的字符对象,u存放的是name数组元素的地址,而*u是目标对象的地址。