C语言之指针

指针

内存

存储器:存储数据的器件

外存

外部存储器,长期保存,非易失性,掉电不会丢失数据

常见的外存设备:硬盘、u盘、光盘、等。

内存

内存又叫内部存储器,掉电会丢失数据(运行内存)

常见设备:RAM(随机存储器)、DDR3、等。

物理内存:实实在在的存储设备

虚拟内存:操作系统虚拟出来的,代码中你看到的地址都是虚拟地址。

在32位操作系统中:32bit->32根总线(一个地址放一个字节)->2的32次方个地址->2的32次方个字节
请添加图片描述

指针的概念

系统给虚拟内存的每一个内存单元分配了一个编号,这个编号就是指针,指针就是地址。

指针变量:指针变量是用来存储一个地址编号

在32位平台中,地址总线是32位,地址编号也是32位,指针变量是32位即4个字节

注意

1.无论什么类型的地址,都是存储单元的编号,在32位系统中都是4个字节

即任意类型的地址都是4个字节

2.无论什么类型的指针只能存放对应类型的变量。

拓展:

字符变量char ch=‘a’;ch占一个字节,它有一个地址编号,这个地址编号就是ch的地址

整型变量:int a=0x12345678;a占4个字节的存储单元,有4个地址编号,以第一个地址,作为变量a的地址。

大端模式(大端序):高地址保存到内存的低地址

小端模式(小端序):高地址保存到内存的高地址

是大端序还是小端序,取决于电脑。

#include<stdio.h>
int main(){
    int a = 0x12345678;
	char *p = (char *)&a;
	if (*p == 0x78){
		printf("little endian\n");
	}else{
		printf("big endian\n");
	}
    
    return 0;
}

指针的定义方法

1.简单的指针变量

数据类型*指针变量

int *p;// 定义了一个指针变量p

在定义指针变量的时候 *是修饰符的意思,修饰的是指针变量,变量名是p

2.关于指针的运算符

&取地址 *取值(解引用)

int a = 0x12345678;
int b;
int *p;
p = &a; // 把a的地址给p赋值,&取地址
b = *p; // 把p指向的a的值赋值给b。*解引用

在调用的时候*是取值。

在一行中定义多个指针变量,每个指针都需要加修饰。

int *p,*q; //定义两个整型的指针变量p和q
int *p,q; //定义整型指针变量p、整型变量q

一般在定义指针变量的时候如果一开始不赋值,则应该让他指向NULL,否则会出现野指针

3.指针的大小

在32位的系统中,任何类型的指针都是4个字节。

在64位的系统中,任何类型的指针都是8个字节。

4.指针的分类

按照指向的数据类型进行分类

  1. 字符指针

    字符型数据的地址

    char *p;// 只能存放字符型数据
    char ch;
    

    …其它基本类型同理。

  2. 函数指针

    int (*fun)(int);--->fun->指向一个返回值为int类型,传参为int类型的函数
    int function(int a){
        return 0
    }
    fun = function;
    
  3. 结构体指针

    struct stu *a;
    struct stu Lihua;
    a = &Lihua;
    
  4. 数组指针

    int (*a)[2][2];----->a指向一个22列的整型数组
    int b[2][2];
    a = b;
    
  5. 指针的指针(二级指针)

    int **q;
    int *p;
    int **q = &p;
    
  6. void * (通用指针)

    #include <sys/mman.h>
    void *mmap(void *addr, size_t length, int prot, int flags,
                      int fd, off_t offset);
    函数的返回值:映射的地址--》返回值为啥void*类型的
    地址-int *p = mmap();*p=0x00ff 0000;
    	  char *p=mmap();*p=0x00;
    

​ 总结:多字节变量,占多个存储单元,每一个存储单元都有它的地址编号,C语言规定,存储单元编号最小的那个就是该多字节变量的地址

5.指针与变量
6.指针与数组元素之间的关系
  • 变量是存放在内存中,有地址编号,我们定义的数组,由多个相同类型的变量集合每一个变量占的内存空间都有地址编号,指针变量就可以存放数组元素地址

    int a[5];
    int *p=NULL;
    p=&a[0];//
    p=a;
    
  • 数组元素引用的方法

    • 方法1:数组加下标

    • 方法2:指针加下标

      int a[5];
      a[2] = 100;
      int *p=NULL;p=a;
      *(p+2) = 100;
      p是a[0]地址,即p+2就是a[2]的地址,a[2]等价于*(p+2)
      p和a不同,p是指针变量,而a是常量,可以给p赋值,不能给a赋值
      p = &a[3];//正确
      a = &a[3];//错误
      
  • 指针的运算

    • 指针加整数结果还是地址
    • 指向同一个数组的指针比大小才有意义,指向前面的指针小于指向后面的指针
    • 指向同一个数组的指针减法,结果是两个指针之间的元素个数
    • 两个相同类型的指针可以相互赋值(void *类型除外)
    • 不同类型的指针相互赋值需要强转

指针数组

概念:指针数组本身是数组,是个指针数组,里面是由数个相同类型的指针变量构成的集合

指针和数组的关系

  1. 指针可以存放数组元素的地址

  2. 数组可以存放指针类型的变量

    int *p[5];//指针数组
    int (*p)[5];//数组指针
    
    #include "stdio.h"
    int main()
    {
    int arr[] = {10, 20, 30, 40, 50};
        int *p = &arr[1];
    	printf("p的地址:%p\n", p);
    	int c = *p++;
    	printf("%d\n", c);
    	printf("*p++后p指向的值%d\n", *p);
    	printf("*p++后p的地址:%p\n", p);
        return 0;
    }
    
    输出:
    p的地址:0xffffcc04
    20
    *p++后p指向的值30
    *p++后p的地址:0xffffcc08
    

指针的指针

(在未特别说明的情况下,以32位系统来讲的)

指针的指针,即指针变量的地址

定义一个指针变量,指针变量本身占四个字节,指针变量也有编号,即指针变量本身也有地址

int a = 0x12345678;  // 假设a的地址为:0x0000 2000
int *p = NULL; //假设指针变量p的地址为:0x0000 3000
p = &a;   //指针变量p中存放着a的地址0x0000 2000
int **q = NULL;
q = &p;  // 指针变量q中存放着指针变量p的地址0x0000 3000,即q指向了p

请添加图片描述

p和q都是指针变量,占4个字节,都存放地址编号。

字符串与指针

字符串的概念

字符串是以’\0’结尾的若干个字符的结合。例如:“hello”

字符串的地址:是第一个字符的地址,字符串"hello"中’h’的地址。

char *str = "hello";

注意点:字符串的存储形式:存储在数组中、存在文字常量区、堆

字符存放在数组中

就是在内存(栈、静态全局区)中开辟一段空间存放字符串

char str[100] = "hello";

注意:普通的全局变量,分配在静态全局区

​ 普通的局部变量,分配在栈区

字符串存放在文字常量区

在文字常量区开辟一段空间存放字符串,将字符串首地址赋值给指针变量

char *str = "hello";
// 定义了一个字符指针变量str,只是
字符串存放在堆区

使用malloc等函数在堆区手动申请空间,将字符串拷贝到堆区

头文件:#include<stdlib.h>

函数原型:void *malloc(size_t size)

函数的作用:向内存申请一块连续的空间,并返回指向内存的首地址

char *st = (char *)malloc(100);
// 动态的申请了100*1字节的
strcpy(str,"hello");
考点:字符串的可修改性

字符串的内容是否可以被修改,取决于字符串放在哪里

  1. 放在数组中的字符串是可以修改的

    char str[100]="hello";
    srr[0]='y';//正确的,可以被修改的
    
  2. 文字常量区内容是不可以被修改的

    char str* = "hello";
    srr[0] = 'y';//错误的。
    //str是指针变量,指向文字常量区,它正向的内容是不可以修改的,但它可以指向别的地方
    str = "yyyy";
    
  3. 堆区的内容是可以被修改的

    char *str=(char *)malloc(100);
    strcpy(str, "hello");
    *str = 'y';//正确的。
    // str是指针变量,指向堆区,它所指向的内容是可以更改的。
    

总结:字符串放在文字常量区不可修改,放在数组或堆区的字符串可以修改。

字符数组的初始化
  1. 字符数组的初始化

    char buf[10] = "hello";
    
  2. 指针指向文字常量区,初始化

    char *buf= "hello";
    
  3. 指向堆区,在堆区存放字符串

    不能一来就初始化,先给指针赋值,让指针指向堆区,再用strcpy、scanf等方法把字符串输入到堆区。

    char *buf=NULL;
    buf=(char *)malloc(100);
    strcpy(buf,"hello");scanf("%s",buf);
    
赋值
  1. 字符数组:使用scanf或者strcpy
char buf[20] = "hello";
buf = "kitty";  // 错误的,数组名是常量,不能给常量赋值
strcpy(buf,"kitty"); // 正确的
scanf("%s",buf); // 正确的
  1. 指针指向文字常量区

    char *buf="hello";
    buf="kitty"; // 正确的,修改了指针变量buf的指向
    
  2. 指向的堆区,在堆区存放字符串

    char *buf=(char *)malloc(100);
    strcpy(buf,"kitty"); //  正确的
    scanf("%s",buf); // 错误的
    

数组指针

1.二维数组

二维数组是有行有列。二维数组看成是由多个一维数组组成的,可以认为二维数组的每一个元素是一维数组。

int a[3][5];

思考:数组名是首元素地址,即第一个元素的地址,是个常量,数组名加一指向下一个元素。

二维数组a中,a+1就是指向下一个元素,即下一个一维数组,即下一行

一维数组a[10];–>a是a[0]的地址即&a[0],a+1是a[1]的地址即&a[1];

二维数组a[3][5];--->对于二维数组 a[3][5],a 是指向一个包含 5 个元素的一维数组的指针,即 a 等价于 &a[0],这里的 a[0] 是一个包含 5 个元素的一维数组。a 的值是该二维数组第一行的首地址,即 &a[0][0]。a + 1 的值是该二维数组第二行的首地址,即 &a[1][0]。

2.数组指针的概念

本身是指针,指向一个数组,加1就跳一个数组,即下一个数组

3.数组指针定义的方法

指向数组的类型 (*指针变量)[元素个数]

int (*a)[5]; // 定义了一个数组指针变量a, a+1跳一个有5个元素的数组,a与a+1相差20个字节
#include<stdio.h>
void main(){
    int a[3][5];
    int (*p)[5];
    printf("a=%p\n",a); // 第0行的首地址
    printf("a+1=%p\n",a+1);// 第1行的首地址,a与a+1相差20个字节
    p=a;
    printf("&a=%p\n",&a); // 整个数组a的首地址
	printf("&a+1=%p\n",&a+1); // 跳了整个数组的地址,与&a相差60个字节
	printf("&a[0]=%p\n",&a[0]); //第0行的首地址
	printf("&a[0]+1=%p\n",&a[0]+1); // 第0行的首地址,&a[0]与&a[0]+1相差20个字节
	printf("&a[0][0]=%p\n",&a[0][0]); //第0行第0列的地址
	printf("&a[0][0]+1=%p\n",&a[0][0]+1); // 第0行第0列的地址,&a[0][0]与&a[0][0]+1相差4个字节
    printf("p=%p\n",p); // 第0行的首地址
	printf("p+1=%p\n",p+1); // 第1行的首地址,p与p+1相差20个字节
}

p[0][0] = 100;等价于a[0][0] = 100;
4.各种数组指针的定义
  • 一维数组指针,加一偏移指向下一个一维数组

    int (*p)[5];
    // 配合每行有5个int类型元素的二维数组来用
    int a[3][5];
    int b[4][5];
    ...
    p = a;
    p = b;
    
  • 二维数组指针,加一偏移指向下一个二维数组

    int (*p)[4][5];
    // 配合每行有4行5列个int类型元素的三维数组来使用
    int a[3][4][5];
    int b[4][4][5];
    ...
    p = a;
    p = b;
    
5.容易混淆的概念
  • 指针数组:是一个数组,是由若干个相同类型的指针构成的集合
int *p[10]; // 数组p中有10个int *类型的指针变量p[0]~p[9]
  • 数组指针:是一个指针,指向一个数组,加1跳一个数组
int (*p)[10]; // p是一个指针,一个数组指针,p+1指向下一个数组,这里跳10个整型变量
  • 指针的指针: 二级指针
int **p;
int *q;
p = &q;
  • 数组名字取地址,就变成了数组指针

    int a[10];
    // a+1跳一个整型元素,即a[1]地址
    // a与a+1相差一个元素,即4个字节
    // a是int *类型
    // &a就变成了一维数组指针,int(*p)[10];
    // (&a)+1和&a相差一个数组,即10个元素,40个字节
    
  • &具有升级的作用,*具有降级的作用,&*相抵消

    // a 和&a 的地址一样但其数据类型不一样。
    int a[4][5];
    a 的数据类型-int (*p)[5];
    &a 的数据类型-int (*p)[4][5];
    
    *(&a[0])<=>a[0]<=>&a[0][0]
    
6.数组名字与指针变量的区别
int a[10];
int *p = a;
相同点
  1. a和p都指向a[0]的地址

  2. 引用数组元素时是等价的

    #include<stdio.h>
    int main(){
        int a[5] = {1,2,3,4,5};
    	int *p = a;
    	printf("%d\n",a[3]);
    	printf("%d\n",p[3]);
    	printf("%d\n",*a+3);
    	printf("%d\n",*p+3);
    	printf("%d\n",*(a+3));
    	printf("%d\n",*(p+3));
    	return 0;
    }
    // 结果都取到了a[3]即4。
    
不同点
  1. a是常量,p是变量,可以给p赋值,不能给a赋值
  2. 对a取地址和对p取地址不一样。

指针作为函数的参数

一级指针作为函数的参数
#include<stdio.h>
void fun(int *c,int *d){
  int *k;
  k = c;
  c = d;
  d = k;
}
int main()
{
  int a = 3;int b = 4;
  int *x = &a;int *y = &b;
  printf("改变前x,y地址:%p %p\n",x,y);
  printf("改变前a,b值:%d %d\n",a,b);
  fun(x,y);
  printf("改变后x,y地址:%p %p\n",x,y);
  printf("改变后x,y值:%d %d\n",*x,*y);
 }
PS E:\code> cd "e:\code\c_code\" ; if ($?) { gcc test1.c -o test1 } ; if ($?) { .\test1 }
fun前x,y地址:0xffffcc0c 0xffffcc08
fun前a,b值:3 4
fun后x,y地址:0xffffcc0c 0xffffcc08
fun后x,y值:3 4
 /*   
。*/

解析:
函数 fun 的分析:
函数 fun 接收两个指针 c 和 d。
在函数内部,创建了一个新的指针 k。
首先,将 c 的值赋给 k,然后将 d 的值赋给 c,最后将 k 的值赋给 d。
这里的问题是,这些操作只是交换了指针 c 和 d 在函数 fun 内部的副本的值,而不是它们在 main 函数中指向的地址。
main 函数的分析:
在 main 函数中,定义了两个整数 a 和 b,并使用指针 x 和 y 分别指向它们。
调用 fun(x,y) 时,实际上是将 x 和 y 的值(也就是 a 和 b 的地址)复制给了 fun 函数的形参 c 和 d。
函数 fun 内部的交换操作仅影响了形参 c 和 d,它们是 x 和 y 的副本,而不是 x 和 y 本身。因此,x 和 y 的值(即存储的地址)以及它们所指向的 a 和 b 的值都不会发生变化

二级指针作为函数的参数
#include<stdio.h>
void fun(int **c,int **d){
  int *k;
  k = *c;
  *c = *d;
  *d = k;
}
int main()
{
  int a = 3;int b = 4;
  int *x = &a;int *y = &b;
  printf("fun前x,y地址:%p %p\n",x,y);
  printf("fun前a,b值:%d %d\n",a,b);
  fun(&x,&y);
  printf("fun后x,y地址:%p %p\n",x,y);
  printf("fun后x,y值:%d %d\n",*x,*y);
 }
PS E:\code> cd "e:\code\c_code\" ; if ($?) { gcc test1.c -o test1 } ; if ($?) { .\test1 }
fun前x,y地址:0xffffcc1c 0xffffcc18
fun前a,b值:3 4
fun后x,y地址:0xffffcc18 0xffffcc1c
fun后x,y值:4 3
/*
*/

解析:
函数 fun 的解释:
fun 函数接收两个二级指针 int **c 和 int **d。
在函数内部,首先声明了一个一级指针 k。
k = *c;:将 *c(也就是 x 的值,即 &a)存储在 k 中。
*c = *d;:将 *d(也就是 y 的值,即 &b)存储在 *c 中,这会导致 x 的值从 &a 变成 &b。
*d = k;:将之前存储在 k 中的值(也就是 &a)存储在 *d 中,这会导致 y 的值从 &b 变成 &a。
main 函数的解释:
首先,定义了两个整型变量 a 和 b,并分别初始化为 3 和 4。
定义了两个指针 x 和 y,分别指向 a 和 b,即 x 存储了 a 的地址,y 存储了 b 的地址。
在调用 fun(&x, &y); 之前,输出 x 和 y 的地址以及它们所指向的值,即 a 和 b 的值。
调用 fun(&x, &y); 时,将 x 和 y 的地址作为参数传递给 fun 函数。
在 fun 函数中,交换了 x 和 y 存储的地址,因此 x 现在存储 y 的原地址,y 存储 x 的原地址。
调用 fun 函数之后,输出 x 和 y 的地址和它们所指向的值。由于 x 和 y 存储的地址交换了,x 现在指向 b,y 现在指向 a。

数组指针作为函数的参数

将数组指针作为函数参数的目的

将数组指针作为函数参数有以下几个重要目的:
处理多维数组:当处理多维数组时,通过数组指针可以方便地传递数组的一部分,而不是整个数组,提高代码的灵活性和可维护性。
提高性能:通过指针传递数组可以避免数组的复制,提高程序的性能,特别是对于大型数组。

#include <stdio.h>

// 函数声明,接受一个指向包含 4 个 int 元素的数组的指针作为参数
void printArray(int (*arrPtr)[4], int numRows) {
    for (int i = 0; i < numRows; ++i) {
        for (int j = 0; j < 4; ++j) {
            // 使用指针解引用的方式访问数组元素
            printf("%d ", (*(arrPtr + i))[j]);
        }
        printf("\n");
    }
}

int main() {
    int arr[3][4] = {{1, 2, 3, 4},
                   {5, 6, 7, 8},
                   {9, 10, 11, 12}};
    // 将数组指针传递给函数
    printArray(arr, 3);
    return 0;
}
/*
代码解释:
在 printArray 函数中,int (*arrPtr)[4] 是一个数组指针,它指向一个包含 4 个 int 元素的数组。
(*(arrPtr + i))[j] 用于访问数组元素,其中 arrPtr + i 表示指针指向第 i 行的起始位置,*(arrPtr + i) 解引用得到第 i 行的数组,然后 [j] 表示访问该数组的第 j 个元素。
在 main 函数中,调用 printArray 时,直接将 arr 传递给函数,因为 arr 作为二维数组名,在传递时会隐式转换为指向其第一行的数组指针。
*/
使用数组指针处理不同大小的二维数组

为了处理不同大小的二维数组,可以将数组的列数作为额外的参数传递给函数。

#include <stdio.h>

// 函数声明,接受一个指向 int 元素数组的指针和列数作为参数
void printArrayGeneric(int (*arrPtr)[], int numRows, int numCols) {
    for (int i = 0; i < numRows; ++i) {
        for (int j = 0; j < numCols; ++j) {
            // 使用指针解引用的方式访问数组元素
            printf("%d ", (*(arrPtr + i))[j]);
        }
        printf("\n");
    }
}

int main() {
    int arr[3][4] = {{1, 2, 3, 4},
                   {5, 6, 7, 8},
                   {9, 10, 11, 12}};
    int arr2[2][5] = {{1, 2, 3, 4, 5},
                   {6, 7, 8, 9, 10}};
    // 将数组指针和列数传递给函数
    printArrayGeneric(arr, 3, 4);
    printArrayGeneric(arr2, 2, 5);
    return 0;
}
/*
代码解释:
printArrayGeneric 函数接受一个数组指针 int (*arrPtr)[],这里 [] 内为空表示可以接受任意大小的一维数组指针。
同时传递 numRows 和 numCols 两个参数,以便函数知道数组的大小,从而正确遍历和打印数组元素。
*/

将指针数组作为函数参数

将指针数组作为函数参数时,需要注意函数的声明和定义。函数声明和定义时,参数应该是一个指针数组的指针,因为数组在传递给函数时会退化为指针。例如:

#include <stdio.h>

// 函数声明
void printStrings(char *arr[], int size);

int main() {
    char *strings[] = {"Hello", "World", "C Programming"};
    int numStrings = sizeof(strings) / sizeof(strings[0]);
    printStrings(strings, numStrings);
    return 0;
}

// 函数定义
void printStrings(char *arr[], int size) {
    for (int i = 0; i < size; i++) {
        printf("%s\n", arr[i]);
    }
}
#include <stdio.h>

// 函数声明
void printStrings(char **arr, int size);

int main() {
    char *strings[] = {"Hello", "World", "C Programming"};
    int numStrings = sizeof(strings) / sizeof(strings[0]);
    printStrings(strings, numStrings);
    return 0;
}

// 函数定义
void printStrings(char **arr, int size) {
    for (int i = 0; i < size; i++) {
        printf("%s\n", arr[i]);
    }
}

在上述代码中,printStrings 函数可以接受一个指针数组作为参数。这里有两种等价的函数声明和定义方式:

  • void printStrings(char *arr[], int size);:使用 char *arr[] 的形式,明确表示这是一个指针数组。
  • void printStrings(char **arr, int size);:使用 char **arr 的形式,这是因为当数组作为参数传递时会退化为指针,所以 char *arr[] 等价于 char **arr
解释
  • main 函数中,我们声明了一个指针数组 strings,其中存储了三个字符串字面量的地址。
  • 计算 strings 数组的元素个数,使用 sizeof(strings) / sizeof(strings[0])
  • 调用 printStrings 函数,将 strings 数组和元素个数传递给它。
  • printStrings 函数中,通过 char **arrchar *arr[] 接收指针数组。这里 arr 是一个指向指针的指针,因为它指向了存储在 strings 数组中的指针元素。
  • 使用 for 循环遍历指针数组,并通过 printf 打印出每个字符串,%s 格式说明符用于打印字符串,arr[i] 指向相应的字符串。
注意事项
  • 当使用指针数组时,要确保指针所指向的数据是有效的,避免使用未初始化或悬空的指针。
  • 在函数中修改指针数组元素指向的数据时,可能会影响到原始数组的数据,因为传递的是指针。

指针作为函数的返回值

1.案例返回局部数组的地址
#include<stdio.h>
char *fun(void);

int main(){
    char *p=fun();
    printf("%s",p);
    return 0;
}
char *fun(void){
    char buf[] = "hello";
    return buf;
}
PS E:\code> cd "e:\code\c_code\" ; if ($?) { gcc test1.c -o test1 } ; if ($?) { .\test1 }
test1.c: 在函数‘fun’中:
test1.c:11:12: 警告:函数返回局部变量的地址 [-Wreturn-local-add]
   11 |     return buf; 

虚拟机打印:段错误

返回地址的时候,地址指向的内容不能是已经释放了的,如果释放掉了的地址,返回就没有意义了。

2.返回静态局部数组的地址

(和全局变量一样)

#include<stdio.h>
char *fun(void);

int main(){
    char *p=fun();
    printf("%s",p);
    return 0;
}
char *fun(void){
    static char buf[] = "hello";
    return buf;
}
PS E:\code> cd "e:\code\c_code\" ; if ($?) { gcc test1.c -o test1 } ; if ($?) { .\test1 }
hello
3.返回文字常量区的地址
#include <stdio.h>
char *fun(void);

int main()
{
  char *p = fun();
  printf("%s", p);
  return 0;
}
char *fun(void)
{
  char *buf = "hello";
  return buf;
}

这里的字符串是存放在文字常量区,内容一直存在

4.返回malloc开辟的空间的地址
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *fun(void);

int main()
{
  char *p = fun();
  printf("%s", p);
  free(p);
  return 0;
}
char *fun(void)
{
  char *buf = (char *)malloc(100);
  strcpy(buf, "hello world");
  return buf;
}
PS E:\code> cd "e:\code\c_code\" ; if ($?) { gcc test1.c -o test1 } ; if ($?) { .\test1 }
hello world

字符串存放在堆区,只要你不释放掉,内容一直存在

指针保存函数的地址(函数指针)

1.函数指针的概念

我们定义的函数,在运行程序的时候,会将函数中的指令加载到内存中的代码中去,所有函数也有起始地址

C语言规定,函数的名字就是函数的首地址,即函数的入口地址

用一个指针变量去存放函数的地址就是函数指针变量

2.函数指针的用处
  1. 函数指针用来保存函数的入口地址
  2. 在项目开发中,会经常碰到编写或者调用带函数指针参数的函数
  3. 在linux系统中,创建多线程的函数,有一个参数是函数指针,接收线程函数的入口地址,即线程创建成功,新的
    任务执行线程函数
 #include <pthread.h>
线程创建函数:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

参数1:线程ID
参数2:设置线程属性->默认写NULL
参数3:你要运行的线程函数的地址
参数4:给线程函数传参
编译要链接线程库
Compile and link with -pthread.

3.函数指针的定义

返回值类型(*函数指针变量名)(形参列表)

一、void * (*fun)(void *)

这是一个函数指针的声明。
fun 是函数指针的名称。
(*fun) 表示 fun 是一个指针,它指向一个函数。
void * 是该函数的返回类型,意味着该函数将返回一个 void * 类型的指针,void * 是一种通用指针类型,可以指向任何类型的数据,但使用时需要进行显式类型转换。
(void *) 是该函数的参数列表,说明该函数接受一个 void * 类型的参数,同样,void * 类型的参数可以接受任何类型的指针,但在函数内部使用时通常也需要进行适当的类型转换

二、int (*p)(int, int)

这也是一个函数指针的声明。
p 是函数指针的名称。
(*p) 表明 p 是一个指针,它指向一个函数。
int 是该函数的返回类型,说明该函数将返回一个整数。
(int, int) 是该函数的参数列表,表明该函数接受两个 int 类型的参数。

#include<stdio.h>
int maxx(int a,int b);
int main()
{
  int a = 4,b = 5;
  int (*p)(int,int);
  p = maxx;
  printf("%d",p(a,b));
	return 0;
}

int maxx(int a,int b)
{
	return a>b?a:b;
}
PS E:\code> cd "e:\code\c_code\" ; if ($?) { gcc test1.c -o test1 } ; if ($?) { .\test1 }
5

调用函数指针与直接调用函数的形式相同

4.函数指针数组
概念

函数指针数组是一个数组,其元素是指向函数的指针,这些指针可以用来存储和调用具有相同参数列表和返回类型的一组函数。

定义

类型名 (*数组名[元素个数]) (参数列表)

类型名 (*数组名[元素个数]) (形参列表)
int (*p[5])(int,int);
定义一个函数指针数组,有5个元素,p[0]~p[4],每一个元素都是函数指针变量
每一个函数指针,指向返回值为int型,形参为两个int

例:使用函数指针数组实现加减乘除的函数调用

#include <stdio.h>

int add(int, int);
int jian(int, int);
int cheng(int, int);
int chu(int, int);
int main()
{
  int (*a[4])(int, int) = {add, jian, cheng, chu};
  int i, n, m;
  char k;
  while (1)
  {
    printf("请输入两个数:\n");
    scanf("%d%d", &n, &m);
    getchar();
    printf("请输入运算符:\n");
    scanf("%c", &k);
    switch (k)
    {
    case '+':
      printf("%d+%d=%d\n", n, m, a[0](n, m));
      break;
    case '-':
      printf("%d-%d=%d\n", n, m, a[1](n, m));
      break;
    case '*':
      printf("%d*%d=%d\n", n, m, a[2](n, m));
      break;
    case '/':
      printf("%d/%d=%d\n", n, m, a[3](n, m));
      break;
    default:
      printf("\n运算符输入错误\n");
      break;
    }
  }

  return 0;
}

int add(int a, int b){
  return a + b;
}

int jian(int a, int b){
  return a - b;
}
int cheng(int a, int b){
  return a * b;
}

int chu(int a, int b){
  if (b != 0)
    return a / b;
  else{
    printf("\n除数不能为0\n");
    return 0;
  }
}

容易混淆的:
int *p(void); // 函数声明;声明函数返回值是int*类型;指针函数。
int (*p)(void); // 用括号括起来,
5.特殊指针
  1. 空类型的指针(void *)

void * 是通用指针,任何类型的指针都可以给void *类型的指针变量赋值

int *p;
void *q;
q = p; // 可以直接赋值,不需要强转

void* 指针变量在32位系统中4个字节,64位系统中8个字节。

  1. NULL(空指针)

    char *p = NULL;//可以认为指向地址编号为0。
    

    在 C 语言中,NULL是一个预定义的宏,通常被定义为(void *)0。

    #include <stdio.h>
    int main()
    {
        int *p = NULL;
        printf("%p", p);
    }
    PS E:\code\c_code> cd "e:\code\c_code\" ; if ($?) { gcc test.c -o test } ; if ($?) { .\test }
    0x0
    

main函数传参

int main(int argc, char *argv[])// argc必须是一个整型变量,argv必须是一个char*类型的指针变量,指向字符串

参数含义:

​ argc(argument count):它是一个整数,表示命令行参数的个数。这个参数的值至少是 1,因为程序名称本身也算一个参数。例如,在命令行中执行./test,此时argc的值为 1;如果是./test arg1 arg2,argc的值为 3。
​ argv(argument vector):它是一个指针数组,其中每个元素都是一个指向字符串的指针。argv[0]指向程序的名称(在某些操作系统中,可能包含完整路径),argv[1]指向第一个命令行参数,argv[2]指向第二个命令行参数,以此类推。

#include <stdio.h>
int main(int argc, char *argv[])
{
    int i;
    printf("argc = %d\n", argc);
    for (i = 0; i < argc; i++)
    {
        printf("argv[%d] = %s\n", i, argv[i]);
    }
    return 0;
}
PS E:\code\c_code> gcc test.c -o test                  
PS E:\code\c_code> ./test heee afsdfa 1111
argc = 4
argv[0] = /cygdrive/e/code/c_code/test
argv[1] = heee
argv[2] = afsdfa
argv[3] = 1111

const

1.修饰普通变量,代表只读的意思。
2.修饰指针
  1. **const char p;等价于char const p;

    意思:指针变量指向的内容不能通过p来修改,用来保护p指向的内容

    open(const char *path, int oflag,...);
    /*
    这是打开函数:用来打开文件的
    参数1:打开文件的路径地址
    为什么用const修饰,就是因为不想你拿到文件的地址就去修改文件的内容。
    */
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    int main(int argc, char *argv[])
    {
        char buf[20] = "hello";
        const char *p = buf;
        strcpy(p,"world"); // 不能修改p指向的内容
        p = "world"; // 能修改指针p的指向
        return 0;
    }
    
  2. *char const p;

    意思是p是只读变量,p是不能指向其他地方的

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    int main(int argc, char *argv[])
    {
        char buf[20] = "hello";
        char *const p = buf;
        strcpy(p,"world"); // 能修改p指向的内容
        // p = "world"; // 不能修改指针p的指向
        printf("%s\n",p);
        return 0;
    }
    
  3. *const char const p;

    意思是既不能改变p的指向也不能改变p所指向的内容

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    int main(int argc, char *argv[])
    {
        char buf[20] = "hello";
        const char *const p = buf;
        // strcpy(p,"world"); // 不能修改p指向的内容
        // p = "world"; // 不能修改指针p的指向
        return 0;
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

繁星北斗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值