C语言——指针
指针
基本概念
指针的概念
引例1:答疑教室
期末考试前,主讲教师在最后一市课通知学生下周会有的助教答疑,但是答疑教室还没有确定。主 讲教师皆诉学生:答疑那天,在教研室的疑教室的房间号。假设学生已经知道教研室的房间号102,请问如何找到助教?
答疑教室的房间号相当于变量rs0m的地址。由于变量room的存储地址存放在变量中,则可以通过变量p取出变量room的存储地址,从而实现对变量class的间接访问。
设置变量room表示答疑教室,p代表教研室,算法如下
step1:安排答疑助教, 为变量room赋值 step2:将变量room的存储地址存放在变量p中 step3:通过变量p获得变量room的值
#include "stdio.h"
int main(){
int room = 1582;
int *p = NULL;
p = &room;
printf("教室房间号是:%X\n", p);
printf("助教的教师编号是:%d\n", *p);
return 0;
}
//教师编号表示答疑助教。用取地址运算符&获得变量ro0m的地址并赋给指针变量p,再通过指针p访问变量room
为了正确理解指针,必须在机器层面理解变量在内存中的存储万式,深刻理解变量地址的含义。
指针变量的定义
指针变量的定义的一般形式如下
基类型 *指针变量名; int *a;
其中,
基类型
是该指针变量指向内存单元的数据类型,可以是任意合法的数据类型;“*
” 称为指针定义符
,用来说明指针变量以区别于普通变量;指针变量名是合法的标识符。“*”称为间接引用运算符;
指针变量必须指向某个确定的存储单元
含义:访问指针变量所指存储单元
指针变量需要注意的问题
定义指针变量时需要明确该指针所指向的数据类型,即该指针所指向的内存单元可以存放什么类型的数据,对 指针变量的运算与它所指向的数据类型密切相关。
int x, *p;//指针变量p只能指向int类型的变量 double y, *q;//指针变量q只能指向double类型的变量。
定义指针变量时,“”是指针定义符,用来说明该变量是指针变量;对指针变量进行间接访问时,“”是间接引用运算符,用来访问指针所指存储单元。
int num; int *p = # num = 100; *p = 100;
指针变量存放该指针指向变量的存储地址,内存地址通常是一个无符号整数,因此所有指针变量占有相同大小的存储空间,具有占用的存储空间单元数与计算机系统和编译器有关。
变量的访问方式
直接访问:通过变量名对变量所占存储单元进行访问(即存取操作),这种访问方式称为直接访问
int x = 10; //对变量x进行存操作,x体现左值特征——变量的地址
int y = x; //对变量x进行取操作,x体现右值特征——变量的值
间接访问:通过指针对指针所指向变量的存储单元进行访问,这种访问方式称为变量的间接访问
int x = 10, y; int *p = &x; *p = 10; //间接访问——存操作 y = *p;//间接访问——取操作
指针变量的初始化
int *p;
int *p = NULL; //NULL为空指针
系统为变量p分配存储空间,由于尚未给指针变量p赋值,则指针阿斌了p是“
值无定义的
”,可能指向内存中任意位置,这种指针称为野指针
。野指针在程序中是很危险的,可能会引发
系统崩溃
良好的编程习惯是初始化空指针为
NULL
。初始化指针变量可以將
变量的存储地址
或是另一个已经定义的指针
赋给该指针变量。int x = 10; int *p = &x; int *q = p;
在指针变量初始化的时,赋值运算符左侧内存地址的数据类型应该与基类型一致
int x = 10; double *q = &x;//指针q的基类型是double,不能指向int类型变量 int x = 10, *p=&x; double *q = p;//指针q和指针p的基类型不一致
指针变量的赋值
int x = 10;
int *p = NULL, *q = NULL; // 指针p和q均初始化为空
p = &x;// 指针p指向变量x
q = p;// 指针q指向变量p所指内存单元
通用指针
是可以指向任意类型数据的指针定义通用指针的一般形式如下:
void *指针变量名;
其中,"*"是指针定义符;void *表示通用指针;指针变量名是一个合法的标识符。
含义:定义通用指针,编译器为该指针变量分配存储空间
#include "stdio.h" int main(){ void *void_ptr = NULL;//定义void_ptr为通用指针并初始化为空 int *int_ptr, num = 10; double *double_ptr, radius = 2.5; void_ptr = #//将void_ptr可以指向int型变量 int_ptr = (int *)void_ptr;//将void_ptr强制类型转换 void_ptr = &radius;//void_ptr可以指向double型变量 double_ptr = (double *)void_ptr;//将指针void_ptr强制类型转换 return 0; }
int *p = NULL; //p为指向int型数据的指针变量
p = 100;//编译器会给出警告
指针与函数传参
引例2:数据交换问题
- 阅读以下两段程序,理解并分析为什么会有不同的运行结果?
#include "stdio.h"
void swap(int a, int b){
int temp = a;
a = b;
b = temp;
}
int main(){
int x = 5, y = 9;
printf("swap函数调用前: x = %d y = %d\n", x, y);
swap(x, y);
printf("swap函数调用后: x = %d y = %d\n", x, y);
return 0;
}
执行流程
#include "stdio.h"
void swap2(int * a, int * b){
int temp = *a;
*a = *b;
*b = temp;
}
int main(){
int x = 5, y = 9;
printf("swap函数调用前: x = %d y = %d\n", x, y);
swap2(&x, &y);
printf("swap函数调用后: x = %d y = %d\n", x, y);
return 0;
}
传值方式
- 在
函数定义
时,将形参定义为普通类型(即菲指针类型),实参可以是类型与形参相容的常量、变量或表达式; - 在
函数调用
时,系统为形参分配存储空间,然后将实参的值传递到形参中; - 在
调用结東
后,系统自动释放形参的存储空间。 - 值传递方式的特点:被调用函数的执行
不影响函数的实参
,即在被调用函数中不能对函数的实参进行修改,因此,通常以值传递方式实现函数的输入。
传地址方式
- 在
函数定义
时,将形参声明为指针类型,实参以是基类型与形参相容的指针大变量地址; - 在
函数调用
时,系统为形参分配存储空间,并将实参的间(即地址)传递到形参中; - 在
调用结束
后,系统自动释放形参的存储空间。 - 指针传递方式的特点:在被调用函数中可以对实参地址所对应的存储单元进行访问,即可以读取或修改该内存单元的值。因此,可以通过指针传递方式实现函数的输出,即将被调用函数修改的值传递(返回)给调用者。
指针与一维数组
指针与一维数组
引例3:顺序查找
要求用指针实现:在整数集合r中顺序查找与给定值key相等的元素
用一维整型数组a[n]存储整数集合,顺序查找从数组的第一个元素开始,依次比较每一个元素,直至找到key为止,如果数组的全部元素都比较完了,说明查找失败。
#include "stdio.h" #define N 6 int main(){ int a[N] = {2, 4, 8, 6, 5, 3}, i, key, index = 0; key = 8; for (int i = 0; i < N; i++) { if (a[i] == key){ index = i; break; } } if (i == N) index = -1; if (index == -1) printf("查找失败!\n"); else printf("查找功能,元素%d在集合中的序号是%d\n", key, index); return 0; }
#include "stdio.h" #define N 6 int main(){ int a[N] = {2, 4, 8, 6, 5, 3}, i, key, index = 0; key = 8; for (int i = 0; i < N; i++) { if (a[i] == key){ index = i; break; } } if (i == N) index = -1; if (index == -1) printf("查找失败!\n"); else printf("查找成功,元素%d在集合中的序号是%d\n", key, index); return 0; }
对一维数组元素的访问共有三种方式:
通过下标访问数组元素:
int a[10]; for(int i=0;i<10;i++) a[i] = i;
通过地址访问数组元素
int a[10]; for(int i=0;i<10;i++) *(a+i) = i;
通过指针访问数组元素:
int a[10]; int *p = a; for(int i = 0;i<10;i++) *(p+i) = i;
p++自增运算要比p+i算术运算快得多,因此其执行效率比地址方式高。
int a[10];
int *p = a;
for(int i = 0;p<(a+10);i++,p++)//表达式三是逗号表达式
*p = i;
指针变量的算术运算
指针变量的加减
运算。对指针变量加上或减去一个整数,表示将该指针后移或前移确定的存储单元两个同类型指针的减法
运算。对两个相同类型的指针变量进行减运算,表示这两个指针间有多少存储单元。对指针变量加上或减去一个浮点数、进行乘、除等算术运算,对西个指针变量进行加、乘、除等算术运算通常没有实际意义
因为数组占用一段连续的内存空间,因此,指针的算术运算通常用于数组
int num, a[5] = {1, 2, 3, 4 ,5} int *p = a, *q; q = p + 4; num = q - p;//4 num = (int)q-(int)p;//16
指针变量的关系运算
- 通常对指向同一数组中的指针变量之间进行比较大小具有实际意义。
- 如果表达式p>q成立,则指针p所指数组元素在指针q所指数组元素的后面;
- 假设数组a有10个元素,如果表达式p<a+10成立,则指针p指向数组中某元素。
一维作为函数的参数
#include "stdio.h"
void swap(int * a, int * b);
void input(int * nums, int n);
void output(int * nums, int n);
void process(int * nums, int n);
void process2(int * nums, int n);
int main(){
int a[10];
input(a, 5);
process(a, 5);
output(a, 5);
return 0;
}
void swap(int * a, int * b){
int temp = *a;
*a = *b;
*b = temp;
}
void input(int nums[], int n){
printf("请输入%d个数字:", n);;
for (int i = 0; i < n; i++)
{
scanf("%d", &nums[i]);
}
}
void output(int nums[], int n){
printf("调整后:");
for (int i = 0; i < n; i++)
{
if (i == 0){
printf("%d", nums[i]);
}else{
printf(" %d", nums[i]);
}
}
printf("\n");
}
void process(int nums[], int n){
int min, max;
min = max = 0;
for (int i = 0; i < n; i++)
{
if (nums[i] < nums[min]) min = i;
if (nums[i] > nums[max]) max = i;
}
if (min != 0){
swap(&nums[0], &nums[min]);
}
if (max != n - 1){
if (max != 0){
swap(&nums[n - 1], &nums[max]);
}else{
swap(&nums[n - 1], &nums[min]);
}
}
}
void process2(int * nums, int n){
int *p;
int *min, *max;
p = max = min = nums;
for(;p < nums + n;p++){
if (*p < *min) min = p;
if (*p > *max) max = p;
}
if (min != nums){
swap(nums, min);
}
if (max != nums + n - 1){
if (max != min){
swap(min, nums + n - 1);
}else{
swap(max, nums + n - 1);
}
}
output(nums, n);
}
指针与二维数组
指针与二维数组
- 如何理解二维数组?我们以二维的方式理解二维数组,它在存储却是一维的
- 把二维数组的每一行看成是一个一维数组,其中每个数组元素是一个一维数组。
- 通过指针访问二维数组的不同形式
用指针访问二维数组
指向字符串的二维数组
char *str[3] = {"Red", "Green", "Blue"};
#include "stdio.h"
void average(int * score);
void search(int (*score)[4], int n);
int main(){
int score[][4] = {{65, 70, 70, 60}, {80, 87, 90, 81}, {90, 99, 100, 98}};
average(&score[0][0]);
search(score, 2);
return 0;
}
void average(int * score){
int sum = 0;
int * p_end = score + 12, *p = score;
while(p != p_end){
sum += *p++;
}
printf("avg = %.2f\n", sum/12.0);
}
void search(int (*score)[4], int n){
int i;
printf("第%d个学生的成绩为:", n);
for (int i = 0; i < 4; i++)
{
printf("%d ", *(*(score+n)+i));
}
printf("\n");
}
二维数组作为函数的参数
#include "stdio.h"
#include "math.h"
void swap(int *a, int *b);
void inverse1(int *p, int n);
void inverse2(int (*p)[4]);
int main(){
int a[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
// inverse1(&a[0][0], 12);
inverse2(a);
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%2d ", a[i][j]);
}
printf("\n");
}
return 0;
}
void inverse1(int *p, int n){
int *q = p, *q_end = p + n - 1;
while(q < q_end){
swap(q, q_end);
q_end--;
q++;
}
}
void inverse2(int (*p)[4]){
int (*q)[4];
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 4; j++)
{
if (i == 1 && j == 2){
break;
}
swap(&p[i][j], &p[2-i][3-j]);
}
}
}
void swap(int *a, int *b){
int temp = *a;
*a = *b;
*b = temp;
}
指针函数与函数指针
指针函数的定义(返回指针的函数)
指针函数的定义 函数声明:返回值* 函数名(参数类型序列); 函数定义:返回类型* 函数名(形式参数序列){
函数体 return 返回指针类型变量;
}
指针函数调用 char *q; q = 函数名(实际参数列表) q与被调用函数的返回类型一致
#include "stdio.h"
const char * getWord(char c){
switch(c){
case 'A':return "Apple";break;
case 'B':return "Banana";break;
case 'C':return "Cat";break;
case 'D': return "Dog";break;
default: return "None";
}
}
int main(){
char input;
printf("Please input a character:");
input = getchar();
printf("%c\n", input);
printf("%s\n", getWord(input));
return 0;
}
指针函数,就是函数的返回值为指针类型。在这个例子中,输入字母后,返回字母对应的字符串。getWord函数的返回值为字符串的首地址。
#include "stdio.h"
const char * getWord(char c){
char str1[] = "Apple";
char str2[] = "Banana";
char str3[] = "Cat";
char str4[] = "Dog";
char str5[] = "None";
switch(c){
case 'A':return str1;break;
case 'B':return str2;break;
case 'C':return str3;break;
case 'D': return str4;break;
default: return str5;
}
}
int main(){
char input;
printf("Please input a character:");
input = getchar();
printf("%c\n", input);
printf("%s\n", getWord(input));
return 0;
}
注意:不要返回局部变量的指针(数组》。在这个例子中,str1数组是局部变量,这个字符数组在子程序结束后,它所对应的存储空间会被释放。
(函数指针)指向函数的指针
函数指针的定义和调用
- 函数指针顾名思义就是指向函数起始地址的指针
- 函数指针定义 函数返回类型名(*指针变量名)(函数指针类型列表) int (*fptr)(int, int);
- 函数指针的使用
- 指针变量名=被指向函数名; fptr = maxx;
- 函数调用:指针变量名(函数调用时的实际参数) int x = ptr(5, 8);
#include "stdio.h"
int square(int num){
return num*num;
}
int main(){
int num;
int (*fp)(int);
printf("please input a number:");
scanf("%d", &num);
fp = square;
printf("fp=0x%x, %d\n", fp, (*fp)(num));
// printf("fp=0x%x, %d\n", fp, fp(num));
return 0;
}
函数名等于函数的地地址。
通过sp=square;语句使得fp指向函数的入口,因此通过fp指针就能找到函数square的代码存放在哪里,从而调用并执行这段代码。
函数指针的说明
函数指针描一类函数的共同特征,这些函数有相同的形式参数类型序列,相同的返回类型。
函数指针是指针变量,可以指向函数的入口地址,上面函数square的入口地址:0x401340(每次运行 都不一定相同)
函数指针与指针函数不冋,函数指针是一个指针变量,它可以指向符合定义过程描叙的任何函数;指针函数是一个函数,函数返回值的类型是一个指针。
函数指针作为参数进行传递
#include "stdio.h"
int add(int num1, int num2){
return num1 + num2;
}
int sub(int num1, int num2){
return num1 - num2;
}
int calculate(int (*fp)(int, int), int num1, int num2){
return fp(num1, num2);
}
int main(){
printf("3 + 5 = %d\n", calculate(add, 3, 5));
printf("3 - 5 = %d\n", calculate(sub, 3, 5));
return 0;
}
定义一个函数指针数组
#include "stdio.h"
void function1(int);
void function2(int);
void function3(int);
int main(){
void (*f[3])(int) = {function1, function2, function3};
int choice;
printf("输入一个数字:范围[0~2]:");
scanf("%d", &choice);
while(choice >= 0 && choice < 3){
(*f[choice])(choice);//(*f[choice])(choice)
printf("输入一个数字:范围[0~2]:");
scanf("%d", &choice);
}
printf("程序运行结束。");
return 0;
}
void function1(int a){
printf("我是1 : %d\n", a);
}
void function2(int a){
printf("我是2 : %d\n", a);
}
void function3(int a){
printf("我是3 : %d\n", a);
}
指针与结构体
结构体变量的传参
//返回结构体变量
#include "stdio.h"
typedef struct Point{
int x;
int y;
}Point;
void Display(Point point){
printf("x is %d\n", point.x);
printf("y is %d\n", point.y);
}
Point SetPoint(int x, int y){
Point point;
point.x = x;
point.y = y;
return point;
}
int main(){
Point point;
point=SetPoint(2, 3);
Display(point);
return 0;
}
x is 2 y is 3
//传值方式传参
#include "stdio.h"
typedef struct Point{
int x;
int y;
}Point;
void Display(Point point){
printf("x is %d\n", point.x);
printf("y is %d\n", point.y);
}
void SetPoint(Point point){
point.x = 2;
point.y = 3;
}
int main(){
Point point;
SetPoint(point);
Display(point);
return 0;
}
x is 228401189 y is 1
结构体数组的传参
#include "stdio.h"
const int N = 2;
struct StudentType{
char no[10];
double foreign;
double spec;
double total;
};
void InputMarks(struct StudentType studnet[], int n);
void AddMarks(struct StudentType studnet[], int n);
void OutputMarks(struct StudentType studnet[], int n);
int main(){
struct StudentType student[10];
int n;
printf("请输入学生人数:");
scanf("%d", &n);
InputMarks(student, n);
AddMarks(student, n);
printf("每个学生的总成绩为:\n");
OutputMarks(student, n);
return 0;
}
void InputMarks(struct StudentType studnet[], int n){
for (int i = 0; i < n; i++)
{
printf("请输入%d考生考号: ", i+1);
scanf("%s", studnet[i].no);
printf("请输入%d考生外语成绩: ", i+1);
scanf("%lf", &studnet[i].foreign);
printf("请输入%d考生专业课成绩: ", i+1);
scanf("%lf", &studnet[i].spec);
}
}
void AddMarks(struct StudentType studnet[], int n){
for (int i = 0; i < n; i++)
{
studnet[i].total = studnet[i].foreign + studnet[i].spec;
}
}
void OutputMarks(struct StudentType studnet[], int n){
for (int i = 0; i < n; i++)
{
printf("%s的总分是%5.1lf\n", studnet[i].no, studnet[i].total);
}
}
结构体与指针
回顾前面的例子
//传值方式传参
#include "stdio.h"
typedef struct Point{
int x;
int y;
}Point;
void Display(Point point){
printf("x is %d\n", point.x);
printf("y is %d\n", point.y);
}
void SetPoint(Point point){
point.x = 2;
point.y = 3;
}
int main(){
Point point;
SetPoint(point);
Display(point);
return 0;
}
使用结构体指针
#include "stdio.h"
typedef struct Point{
int x;
int y;
}Point, *pPoint;
void Display(Point point){
printf("x is %d\n", point.x);
printf("y is %d\n", point.y);
}
void SetPoint(pPoint point){
point->x = 2;
point->y = 3;
}
int main(){
Point point;
SetPoint(&point);
Display(point);
return 0;
}
指向结构体数组的指针
动态内存分配
系统内存分区