第八章节 ⭐⭐⭐指针 重点章节
——选择、填空、大题
本章节为重点内容,C语言的精华,是之后结构体和文件的重要基础,此章节我会花大篇幅的时间来进行整理。
8.1 指针是什么
(1)指针就是地址 而 指针变量是用来存放地址的变量 ! 指针变量的值才是一个地址。
(2)一个地址型的数据包含三个信息:①内存编号的纯地址②它本身的类型,及指针类型③以它为标识的存储单元中是什么类型的数据,即基类型
8.2 指针变量
存放地址的变量是指针变量
类型名 *指针变量名
整型变量:
int a,b;
int *p1,*p2;
p1 = &a; p2 = &b; //这里的&不仅包含变量a的位置,还包括存储的数据是整型的信息。
通过指针引用数组:
int a[5] = {1,2,3,4,5};
int *p1;
p1 = &a[0]; or p1 = a; //将a[0]元素的地址赋给指针变量p,数组名不代表整个数组,只代表数组的首元素的地址
或者: //引用:p1 + i 就是第i个元素的地址,*(p + i) 就是具体元素
int *p2 = a;//同上一样
int (*p3)[5];//数组指针
p3 = &a;//一维数组不能写成p=a,p=a表示p的值是&a[0],而我们要将p指向一维数组行。 引用: (*p3)[3]的值是4,*(*p3 + 3)
int main(){//对数组指针赋值
int a[N] i;
int (*b)[N];
b = &a;
for (i = 0; i < N;i++)
scanf("%d", *b + i);//加个*指向列,列+i,指向元素地址
for (i = 0; i < N;i++)
printf("%d\t", (*b)[i]);//*(*b+i)
return 0;
}
通过指针引用多维数组
int a[3][4] = {{1,3,5,7},{9,11,13,15},{17,19,21,23}};
int *p1;
p1 = a[0];//p1指向整型的数组元素a[0],p1指针变量存放的a[0]元素地址 ,引用具体元素: *( p1 + i)
****值得注意的是,指针变量只能通过以上方式对多维数组初始化,将二维数组的首元素的地址指向指针变量****
int (*p2)[4];//数组指针
p2 = a;//指向二维数组的第0行 ,引用具体元素: *(*(p2 + i) + j)
p2 = a + n;//指向二维数组的第n行地址
通过指针引用字符串
char *str = "i love sleep" ;
char *str1[3] = {"i love you","i hate you","i miss you"};
8.2.4 指针变量作为函数参数
值得注意的是不能通过执行调用函数来改变指针变量的值,只能通过调用函数的指针变量指向的变量的值。如下:
//书上例题8.3,输入三个数,大小顺序输出,8.4,8.5
#include<stdio.h>
int main(){
void swap1(int *p1,int *p2);
void swap2(int *p1,int *p2);
void exchange(int *q1, int *q2, int *q3);
int a, b;
int *pointer1, *pointer2;
scanf("%d%d", &a, &b);
pointer1 = &a;
pointer2 = &b;
if(*pointer1 < *pointer2)
swap1(pointer1, pointer2);
printf("swap1 max = %d\tmin = %d\n",*pointer1 , *pointer2);
if(*pointer1 < *pointer2)
swap2(pointer1, pointer2);
printf("swap2 max = %d\tmin = %d\n",*pointer1 , *pointer2);
int c, d, e;
int *q1, *q2, *q3;
scanf("%d%d%d", &c, &d, &e);
q1 = &c;
q2 = &d;
q3 = &e;
exchange(q1, q2, q3);
printf("exchange c = %d \td = %d\te = %d\n", *q1, *q2, *q3);
return 0;
}
void swap1(int *p1,int *p2){//这样是不能交换的,c语言实参变量和形参变量之间数据传递是单向的“值传递”
//用指针变量作函数参数时同样遵守这一规则。不可能通过调用函数来改变实参变量指针的值。
int *t;
t = p1;
p1 = p2;
p2 = t;
}
void swap2(int *p1,int *p2){
//不可能通过调用函数来改变实参变量指针的值,但是可以改变实参指针变量所指向的值。如下:
int t;
t = *p1;
*p1 = *p2;
*p2 = t;
}
void exchange(int *q1,int *q2,int *q3){
if(*q1 > *q2)
swap2(q1, q2);
if(*q1 > *q3)
swap2(q1, q3);
if(*q2 > *q3)
swap2(q2, q3);
}
8.3 通过指针引用数组
①下标法 a[i]
②指针法 *(a + i) 或者 *(p + i) 其中a是数组名,p是指针名,其初值是p = a。
//例题8.8 将数组中的n个数逆序
#include<stdio.h>
#define N 10
int main(){
void exchange1(int *p);
void exchange2(int a[]);
int a[N] ={1,2,3,4,5,6,7,8,9,10};
int *p;
// for (p = a; p < a + N;p++)
// scanf("%d", p);
printf("one:\t");
for (p = a; p < a + N;p++)
printf("%d\t", *p);
printf("\n");
printf("two:\t");
p = a;//一定记得将指针重新指回数组首地址
exchange1(p);//值得注意的是,如果将指针变量作为实参,必须先将指针变量指向确定的值,指向一个已定义的对象
for (p = a; p < a + N;p++)
printf("%d\t", *p);
printf("\n");
printf("three:\t");
exchange2(a);
for (p = a; p < a + N;p++)
printf("%d\t", *p);
return 0;
}
void exchange1(int *p){//用指针交换,一定要定义两个指针,值得注意的是,使用指针的时候不可以使用数组的下标的方法对其进行处理
int temp;
int *t,*k;
t = p;
k = p + (N - 1);
for (; t < p + (N / 2); t++,k--)
{
temp = *t;
*t = *k;
*k = temp;
}
}
void exchange2(int a[]){//用数组法
int t, i, j = N;
for (i = 0; i < N / 2; i++)
{
t = a[i];
a[i] = a[j - i - 1];
a[j - i - 1] = t;
}
}
8.4 通过指针引用字符数组
赋值规则如下:
对 字符指针 变量进行赋值:
char *a; //a为字符指针变量
a ="i am a boy." //赋给a的不是字符串,是第一个元素的地址
对 字符指针变量和数组 的初始化:
char *a="i love you";
等价于
char *a;
a = "i love you";
对数组的初始化
char str[14] = "i love you";
但是不能用赋值语句对字符数组中全部元素整体赋值,如下。
char str[14];
str[14] = "i love you";
字符串指针数组:
*str[5] = {"abc" , "abc" , "abc" , "abc" , "abc"};
//用指向指针的指针的方法对5个字符串排序输出
#include<stdio.h>
#include<string.h>
#define LINEMAX 20
int main(){
void sort(char **p);
int i;
char **p,str[5][LINEMAX],*pstr[5] = {"China" , "America" , "India" ,"Philippines","Canada"};
p = pstr;
sort(p);
printf("\nstrings sorted:\n");
for(i = 0;i < 5 ;i++){
printf("%s\n",pstr[i]);
}
return 0;
}
void sort(char **p){
int i ,j;
char *temp;
for(i = 0;i < 5;i++){
for(j = i + 1; j < 5 ; j++){
if(strcmp(*(p + i) , *(p + j)) > 0){
temp = *(p + i);
*(p + i) = *(p + j);
*(p + j) = temp;
}
}
}
}
程序示例如下:
#include<stdio.h>
int main(){
void change1(char a[], char b[]);
void change2(char p1[], char p2[]);
void change3(char *a, char *c);
char a[] = "i am a clear boy.", b[20], *c = "i am a bad boy.";
char *p1, *p2;
p1 = a;
p2 = b;
change1(a, b);//字符串数组名作为参数
change2(p1, p2);//实参为字符型指针变量
change3(a, c);//*c为字符指针变量
printf("a = %s\nc = %s", a, c);
return 0;
}
void change1(char a[], char b[]){//字符串数组名作为参数
}
void change2(char p1[], char p2[]){//但是形参为字符串数组
}
//值得注意的是字符指针变量是不可以被修改的 *c 不可被修改
void change3(char *a, char *c){
for (; *c != '\0';a++,c++){
*a = *c;
}
*a = '\0';
/****这里两个指针指向了最后的'\0',所以直接输出是不对的******/
// printf("a = %s\nc = %s", a, c);
}
8.5 指向函数的指针
函数名代表函数的起始地址。
函数名就是函数的指针,它代表函数的起始地址。
指向函数的指针的一个重要用途就是把函数的入口地址作为参数传递到其他函数。如下程序
//通过指针变量调用它所指向的函数。
#include<stdio.h>
int main(){
int fun(int a, int b, int (*p)(int,int));//可以在将函数指针传递到子函数使用
int max(int a, int b);
int min(int a, int b);
int sum(int a, int b);
int a, b, flag;
int (*p)(int, int);
printf("please input a and b:\n");
scanf("%d %d", &a, &b);
printf("please input flag(flag = 0 -> min ; flag = 1 ->max; flag = 2 ->sum):\n");
scanf("%d", &flag);
if (flag == 0)
p = min; //将函数指针p指向函数
else if(flag == 1)
p = max;
else if(flag == 2)
p = sum;
fun(a, b, p);
return 0;
}
void fun(int a,int b ,int (*p)(int,int)){//形参传来的函数的指针
printf("%d\n", (*p)(a, b));//对其进行调用
}
int max(int a ,int b){
printf("max = ");
return a > b ? a : b;
}
int min(int a ,int b){
printf("min = ");
return a < b ? a : b;
}
int sum(int a ,int b){
printf("sum = ");
return a + b;
}
利用函数指针计算书上p272的积分程序,如下:
//编写一个求定积分的通用函数
#include<stdio.h>
#include<math.h>
int main(){
//积分上限,积分下限,原函数
float fun(float a, float b, float (*p)(float));
float p1(float x);//原函数
float p2(float x);
float p3(float x);
float p4(float x);
float a, b;
float (*p)(float);
scanf("%f %f", &a, &b);
p = p1;
printf("fun1 = %0.2f\n", fun(a, b, p));
p = p2;
printf("fun2 = %0.2f\n", fun(a, b, p));
p = p3;
printf("fun3 = %0.2f\n", fun(a, b, p));
p = p4;
printf("fun4 = %0.2f\n", fun(a, b, p));
return 0;
}
float fun(float a, float b, float (*p)(float)){//上限减下限
return (*p)(a) - (*p)(b);
}
float p1(float x){//原函数
return x + x * x / 2;
}
float p2(float x){
return x * x + 3 * x;
}
float p3(float x){
return (x) + x;
}
float p4(float x){
return pow((1 + x), 3) / 3; //直接除3,不要乘1/3
}
8.6 返回指针函数
*类型名 (参数列表) 返回指针
//函数返回指针
#include<stdio.h>
int main(){
float score[][4] = {{65, 56, 70, 60}, {58, 87, 90, 81}, {90, 99, 100, 98}};
float *search(float (*p)[4],int n);
int n;
float (*p)[4],*t;
scanf("%d", &n);
t = search(score, n);
for (int i = 0; i < 4; i++)
{
printf("%0.2f\t", *(t + i));
}
return 0;
}
float *search(float (*p)[4],int n){
float *t;
t = *(p + n);
return t;
}
8.7 指针数组和多重指针
8.7.1 指针数组
一般用指针数组来处理多个字符串
书上p277例题,指向互换。
指针数组是一个数组,数组里面的元素存放的是指针。
*name[] = {
“Follow me”,
“BASIC”,
“Great Wall”,
“FORTRAN”,
“Computer design”};
改变了指针数组内指针的每个元素的指向
//8.27 对指针数组进行排序
#include<stdio.h>
#include<string.h>
int main(){
void sort(char *name[], int n);
void print(char *name[], int n);
char *name[] = {
"Follow me",
"BASIC",
"Great Wall",
"FORTRAN",
"Computer design"};
sort(name, 5);
print(name, 5);
return 0;
}
void sort(char *name[], int n){
char *t;//指针,下面交换的是指向
char m[5] = {'C','H','I','N','A'};
char k;
for (int i = 0; i < n - 1; i++)
{
for (int j = 0; j < n - i - 1 ; j++){
if (strcmp(name[j] ,name[j + 1]) > 0 )
{
t = *(name + j);
*(name + j) = name[j + 1];
name[j + 1] = t;
}
}
}
}
void print(char *name[] , int n){
for (int i = 0; i < n; i++)
{
printf("%s\n", name[i] );
}
}
8.7.2 指向指针数据的指针
8.8 动态内存分配与指向它的指针变量
程序 = 代码 + 数据
静态存储区 存放全局变量等
动态存储区又分为栈 和 堆 :
ps:注意栈和堆的区别,堆只能用指针引用,栈是有名字的
动态内存分配主要通过库函数来实现:#include<stdlib.h>
malloc,calloc,free,realloc
1. malloc 函数:
其原型为: void *malloc(unsigned int size);
· 其作用是再内存的动态存储区(堆:heap)中分配一个指定长度为size的连续存储空间
· 函数返回值是所分配区域的第一个字节的地址,或者说此函数是一个指针型函数,返回指针指向所分配的内存区域的起始位置。
→ 注意指基类型为void * 这种指针叫无类型指针,不指向任何类型的数据,只提供一个地址
→ 如果函数未能成功执行,例如内存空间不足,则返回空指针NULL
→ 一般要对函数的返回值进行强制类型转换,以指向特定的数据如下:
p1 = (struct Student *)malloc(LEN);
p1 = (int *)malloc(100 *sizeof(int));
2. calloc 函数:
其原型为: void *calloc(unsigned n, unsigned size);
· 其作用是再内存的动态存储区中分配n个长度为size的连续空间,这个空间一般比较大,足以保存一个数组
→ 用calloc函数可以为维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size。这就是动态数组。函数返回指向所分配域的起始位置的指针;如果分配不成功,返回NULL。如:
p = (int *)calloc(50 , sizeof(int));
开辟50x4个字节的临时内存区域(可存50个整数),并把起始地址赋给指针变量p
3. free 函数:
其原型为: void free(void *p);
释放指针变量p所指向的动态空间。
4. realloc 函数:
其原型为: void *realloc(void *p, unsigned int size);
如果已经通过malloc函数或者calloc函数获得了动态控件,想改变其大小,可以通过realloc函数重新分配。
小结
1、指针就是地址,指针变量就是地址变量
2、指针就是地址本身,而指针变量是用来存放地址的变量
3、地址就意味着指向
4、void *指针是一种特殊的指针,不指向任何类型的数据
5、一维数组名代表数组首元素的地址,不可以改变,指针常量不可改变
6、表8.4
习题:
0805//N个人,报到3的人退出圈子,最后留下谁
#include<stdio.h>
#define N 5
int main(){
int arr[N], count = 0, p = 0 , i;//i为总记录
int(*a)[N];
a = &arr;
for (i = 0; i < N ; i++){
*(*a + i) = 1;
}
i = 0;
while(count < N ){
if(*(*a + (i % N)) == 0){
i++;
continue;
}else{
if(p == 2){
*(*a + (i % N)) = 0;
printf("i = %d\n", i);
p = 0;
count++;
i++;
continue;
}
p++;
i++;
}
}
printf("fint adress is %d , the count is %d\n", i % N , i);
return 0;
}
0819
//编写一个函数new,对n个字符开辟连续的存储空间,
//此函数返回一个指针地址,指向字符串开始的空间。new(n)表示分配n个字节的内存空间
#include<stdio.h>
#define NEWSIZE 1000 //指定开辟存储区间的最大容量
char newbuf[NEWSIZE]; //定义字符数组newbuf
char *newp = newbuf; //定义指针变量newp,指向可存储区间的始端
char *new(int n){ //定义开辟存储区间的函数new,开辟存储区间后返回指针
if(newp + n <= newbuf + NEWSIZE){ //开辟区域未超过newbuf数组的大小
newp += n; //newp指向存储区的末尾
return newp - n; //返回一个指针,指向开始位置
}
else
return (NULL); //不够分配则返回一个空指针
}