c语言指针知识点及重难点(指针的10种经典应用场合)
c语言指针知识点及重难点(指针的10种经典应用场合)1.5 用于指向函数的函数指针,使用函数指针调用回调函数;1.4 用做函数返回值,返回一个左值;1.1 在函数中用作输出型参数,产生副作用(更新被调函数中的局部变量的值);1.2 在函数中用作输出型参数,用于返回多个值;1.3 在函数中用作输入型参数,指向复合类型,避免传值的副作用(性能损耗);
都说指针是C语言的精髓,那指针究竟有哪些经典应用场合呢?
指针有三大类:指向数据的指针,指向函数的指针和范型指针(void*)。
其经典的应用场合,可以分为以下10类:
1 与函数相关的使用
1.1 在函数中用作输出型参数,产生副作用(更新被调函数中的局部变量的值);
1.2 在函数中用作输出型参数,用于返回多个值;
1.3 在函数中用作输入型参数,指向复合类型,避免传值的副作用(性能损耗);
1.4 用做函数返回值,返回一个左值;
1.5 用于指向函数的函数指针,使用函数指针调用回调函数;
2 用于指向堆内存;
3 与void配合使用,用void*来表示一个泛型指针;
4 用于指向数组名(数组指针);
5 用于指向字符串常量(字符串常量指针);
6 在数据结构中,用作链式存储;
附加:在字符串、文件操作中跟踪操作位置;
1 与函数相关的使用
1.1 在函数中用作输出型参数,产生副作用(更新被调函数中的局部变量的值)
#include <stdio.h>
void demo(int *ap int size int *max)
{
*max=ap[0];
for(int i=1;i<size;i )
if(ap[i]>*max)
*max = ap[i];
}
int main()
{
int max ap[5]={1 2 8 4 5};
demo(ap 5 &max);
printf("%d\n" max);
getchar();
return 0;
}
1.2 在函数中用作输出型参数,用于返回多个值
#include <stdio.h>
#include <math.h>
int equationSolve(double a double b double c double *x1 double *x2)
{
int delta = a*a-4*a*c;
if(delta>=0)
{
*x1 = (-b sqrt(delta))/2*a;
*x2 = (-b-sqrt(delta))/2*a;
return 1;
}
else
return 0;
}
int main(void)
{
double x1 x2;
if(equationSolve(1 3 -14 &x1 &x2))
printf("x1=%.2f\nx2=%.2f\n" x1 x2);
else
printf("无实根!\n");
getchar();
return 0;
}
/*
x1=2.27
x2=-5.27
*/
1.3 在函数中用作输入型参数,指向复合类型,避免传值的副作用(性能损耗)
#include <stdio.h>
typedef struct Inventory{
int sku;
char name[36];
char unit[12];
char supplier[48];
double price;
double stock;
}Inven;
void demo(const Inven *p)
{
printf("The amounts is %f\n" p->price * (*p).stock);
// ……
}
int main()
{
Inven inven={123 "carban fibre" "kg" "uc" 128 100};
demo(&inven);
getchar();
return 0;
}
1.4 用做函数返回值,返回一个左值
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
void printIntArray(void** array size_t length) {
printf("Array at %p\n" array);
while (length--) {
printf(" [%zu] at %p -> %p" length array length *(array length));
if (*(array length)) {
printf(" -> %d" *(int*)*(array length));
}
printf("\n");
}
}
void* getElement(void** array size_t index) {
return *(array index);
}
int main(int argc char** argv) {
const size_t n = 5;
size_t i;
/* n element array */
void** test = malloc(sizeof(void*) * n);
i = n;
while (i--) {
*(test i) = NULL;
}
/* Set element [1] */
int testData = 123;
printf("testData at %p -> %d\n" &testData testData);
*(test 1) = (void*)&testData;
printIntArray(test n);
/* Prints 123 as expected */
printf("Array[1] = %d\n" *(int*)getElement(test 1));
getchar();
return 0;
}
返回左值在C 中应用比较多,特别是用引用来返回左值,如返回ostream&,或重载[]、=等运算符。
1.5 用于指向函数的函数指针,使用函数指针调用回调函数
// 通用的冒泡排序函数的应用
#include <iostream>
#include <cstring>
using namespace std;
template <class T>
void sort(T a[] int size bool (*f)(T T)); // callee
bool increaseInt(int x int y) {return x<y;} // callbackee1
bool decreaseInt(int x int y) {return y<x;} // callbackee2
bool increaseString(char *x char *y) {return strcmp(x y)<0;} // callbackee3
bool decreaseString(char *x char *y) {return strcmp(x y)>0;} // callbackee4
int main() // caller
{
int a[] = {3 1 4 2 5 8 6 7 0 9} i;
char *b[]= {"aaa" "bbb" "fff" "ttt" "hhh" "ddd" "ggg" "www" "rrr" "vvv"};
sort(a 10 increaseInt );
for (i = 0; i < 10; i) cout << a[i] <<"\t";
cout << endl;
sort(a 10 decreaseInt);
for ( i = 0; i < 10; i) cout << a[i] <<"\t";
cout << endl;
sort(b 10 increaseString );
for (i = 0; i < 10; i) cout << b[i] <<"\t";
cout << endl;
sort(b 10 decreaseString);
for ( i = 0; i < 10; i) cout << b[i] <<"\t";
cout << endl;
while(1);
return 0;
}
// 通用的冒泡排序函数
template <class T>
void sort(T a[] int size bool (*f)(T T))
{
bool flag;
int i j;
for (i = 1; i < size; i) {
flag = false;
for (j = 0; j <size - i; j) {
if (f(a[j 1] a[j])) {
T tmp = a[j];
a[j] = a[j 1];
a[j 1] = tmp;
flag = true;
}
}
if (!flag) break;
}
}
/*
0 1 2 3 4 5 6 7 8 9
9 8 7 6 5 4 3 2 1 0
aaa bbb ddd fff ggg hhh rrr ttt vvv www
www vvv ttt rrr hhh ggg fff ddd bbb aaa
*/
2 用于指向堆内存
实质也是通过库函数(malloc.h)返回void*指针。
#include <stdio.h>
#include <malloc.h>
int** demo(int r int c)
{
int **ap = (int**)malloc(sizeof(int*)*r);
for(int i=0;i<c;i )
ap[i]=(int*)malloc(sizeof(int)*c);
return ap;
}
int main()
{
int r=3 c=5;
int* *ap=demo(r c);
int i j;
for(i=0;i<r;i )
for(j=0;j<c;j )
ap[i][j] = (i 1)*(j 1);
for(i=0;i<r;i )
{
for(j=0;j<c;j )
printf("- " ap[i][j]);
printf("\n");
}
getchar();
return 0;
}
/*
1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
*/
3 与void配合使用,用void*来表示一个泛型指针
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int swap2(void *x void *y int size)
{
void *tmp;
if((tmp=malloc(size)) == NULL)
return -1;
memcpy(tmp x size);
memcpy(x y size);
memcpy(y tmp size);
free(tmp);
return 0;
}
int main()
{
int a=3 b=4;
swap2(&a &b sizeof(int));
printf("%d %d\n" a b);
double c=3 d=4;
swap2(&c &d sizeof(double));
printf("%f %f\n" c d);
getchar();
return 0;
}
4 用于指向数组名(数组指针)
#include <stdio.h>
void funcP(int *p int r int c)
{
for(int i=0;i<r*c;i )
printf((i 1)%(r 1)==0 ? "-\n":"- " *p );
printf("\n");
}
void funcAp(int (*p)[4] int r int c) // int p[][4]
{
for(int i=0;i<r;i )
{
for(int j=0;j<c;j )
printf("- " *(*(p i) j)); // p[i][j]
printf("\n");
}
printf("\n");
}
void funcApp(int (*p)[3][4] int r int c)
{
for(int i=0;i<r;i )
{
for(int j=0;j<c;j )
printf("- " *(*(*p i) j)); // (*p)[i][j] 体现解引用指针,产生副作用
printf("\n");
}
printf("\n");
}
int main()
{
int arr[3][4] = {0 1 2 3 4 5 6 7 8 9 10 11};
int size = sizeof arr / sizeof *arr; // 在该上下文中,arr是数组的地址,其类型是int(*)[3][4]
funcP((int*)arr 3 4);
funcAp(arr 3 4); // 在该上下文中,arr是数组首元素的地址,其类型是int(*)[4]
funcApp(&arr 3 4); // 在该上下文中,arr是数组的地址,其类型是int(*)[3][4]
getchar();
return 0;
}
/* output:
0 1 2 3
4 5 6 7
8 9 10 11
0 1 2 3
4 5 6 7
8 9 10 11
0 1 2 3
4 5 6 7
8 9 10 11
*/
5 用于指向一个字符串常量(字符串常量指针)
const char* demo()
{
//char sa[] = "Hello!";
const char *sp = "Hello!";
return sp;
}
关于字符数组和字符指针可以图示如下:
在字符指针数组,数组元素是一个字符指针,用于指向一个字符串常量,如:
char *pMonth[] = {"January" "February" "March" "April" "May" "June" "July" "August" "September" "October" "November" "December"};
char *week[10] = { "Mon" "Tue" "Wed" "Thu" "Fri" "Sat" "Sun"};
char* color[]={"红-red" "橙-orange" "黄-yellow" "绿-green" "青-cyan" "蓝-blue" "紫-purple"};
char *gans[10] = {"甲" "乙" "丙" "丁" "戊" "己" "庚" "辛" "壬" "癸"};
char* zhis[12] = {"子" "丑" "寅" "卯" "辰" "巳" "午" "未" "申" "酉" "戌" "亥"};
char* animals[12] = {"鼠" "牛" "虎" "兔" "龙" "蛇" "马" "羊" "猴" "鸡" "狗" "猪"};
6 在数据结构中,用作链式存储
#define ElementType int
typedef struct LNode {
ElementType data;
struct LNode *next;
} LNode *LinkList;
附加:在字符串、文件操作中跟踪操作位置。
如分割字符串函数strtok():
char *strtok(char s[] const char *delim);
对该函数的一系列调用将str拆分为标记(token),这些标记是由分隔符中的任何字符分隔的连续字符序列。
在第一次调用时,函数需要一个C风格字符串作为str的参数,str的第一个字符用作扫描标记的起始位置。在随后的调用中,函数需要一个空指针,并使用最后一个标记结束后的位置作为扫描的新起始位置。
要确定标记的开头和结尾,函数首先从起始位置扫描未包含在分隔符中的第一个字符(它将成为标记的开头)。然后从这个标记的开头开始扫描分隔符中包含的第一个字符,它将成为标记的结尾。如果找到终止的空字符,扫描也会停止。
标记的末端将自动替换为空字符,函数将返回标记的开头。
在对strtok的调用中找到str的终止空字符后,对该函数的所有后续调用(以空指针作为第一个参数)都会返回空指针。
找到最后一个标记的点由函数在内部保留,以便在下次调用时使用(不需要特定的库实现来避免数据争用)。
#include <string.h>
#include <stdio.h>
int main()
{
char str[80] = "This is - www.runoob.com - website";
const char s[2] = "-";
char *token;
/* 获取第一个子字符串 */
token = strtok(str s);
/* 继续获取其他的子字符串 */
while (token != NULL)
{
printf("%s\n" token);
token = strtok(NULL s);
}
printf("\n");
for (int i = 0; i < 34;i )
printf("%c" str[i]);
return (0);
}
二进制文件的随机读写:
在标记文件信息的结构体FILE中,包含3个标识文件操作位置的指针。
typedef struct _iobuf {
char *_ptr; //文件操作的下一个位置
int _cnt; //当前缓冲区的相对位置
char *_base; //指基础位置(即是文件的其始位置)
int _flag; //文件标志
int _file; //文件的有效性验证
int _charbuf; //检查缓冲区状况 如果无缓冲区则不读取
int _bufsiz; //缓冲区大小
char *_tmpfname; //临时文件名
}FILE;
code demo:
#include<iostream> // 按记录分块读写文件
#include <fstream>
#include<cstdlib>
#include<cstring>
using namespace std;
class Student
{
public:
Student(void) {}
Student(int n char nam[20] float s):
num(n) score(s)
{
strcpy(name nam);
}
void setNum(int n)
{
num=n;
}
void setName(char nam[20])
{
strcpy(name nam);
}
void setScore(float s)
{
score=s;
}
void show()
{
cout<<num<<" "<<name<<" "<<score<<endl; //显示通过<<的重载实现更自然
}
private:
int num;
char name[20];
float score;
};
int main( )
{
Student stud[5]=
{
Student(1001 "Li" 85)
Student(1002 "Fun" 97.5)
Student(1004 "Wang" 54)
Student(1006 "Tan" 76.5)
Student(1010 "ling" 96)
};
fstream iofile("stud.dat" ios::in|ios::out|ios::binary);
if(!iofile)
{
cerr<<"open error!"<<endl;
abort( );
}
cout<<"(1)向磁盘文件输入5个学生的数据并显示出来"<<endl;
int i;
for(i=0; i<5; i )
{
iofile.write((char *)&stud[i] sizeof(stud[i]));
stud[i].show();
}
cout<<"(2)将磁盘文件中的第1 3 5个学生数据读入程序,并显示出来"<<endl;
Student stud1[5];
for(i=0; i<5; i=i 2)
{
iofile.seekg(i*sizeof(stud[i]) ios::beg);
iofile.read((char *)&stud1[i/2] sizeof(stud1[0]));
stud1[i/2].show();;
}
cout<<endl;
cout<<"(3)将第3个学生的数据修改后存回磁盘文件中的原有位置"<<endl;
stud[2].setNum(1012);
stud[2].setName("Wu");
stud[2].setScore(60);
iofile.seekp(2*sizeof(stud[0]) ios::beg);
iofile.write((char *)&stud[2] sizeof(stud[2]));
iofile.seekg(0 ios::beg);
cout<<"(4)从磁盘文件读入修改后的5个学生的数据并显示出来"<<endl;
for(i=0; i<5; i )
{
iofile.read((char *)&stud[i] sizeof(stud[i]));
stud[i].show();
}
iofile.close( );
getchar();
return 0;
}
/*
(1)向磁盘文件输入5个学生的数据并显示出来
1001 Li 85
1002 Fun 97.5
1004 Wang 54
1006 Tan 76.5
1010 ling 96
(2)将磁盘文件中的第1 3 5个学生数据读入程序,并显示出来
1001 Li 85
1004 Wang 54
1010 ling 96
(3)将第3个学生的数据修改后存回磁盘文件中的原有位置
(4)从磁盘文件读入修改后的5个学生的数据并显示出来
1001 Li 85
1002 Fun 97.5
1012 Wu 60
1006 Tan 76.5
1010 ling 96
*/
-End-