前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C 二维数组和指针、函数指针、typedef等的一些笔记

C 二维数组和指针、函数指针、typedef等的一些笔记

作者头像
小锋学长生活大爆炸
发布2022-03-29 13:32:38
5860
发布2022-03-29 13:32:38
举报
文章被收录于专栏:小锋学长生活大爆炸

文章目录

二维函数和指针

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二维数组

代码语言:javascript
复制
char a[5][24] = {0};
printf("%p\r\n", a);
printf("%p\r\n", a[0]);
printf("%p\r\n", *a);
printf("%p\r\n", &a[0][0]);
printf("%d\r\n", sizeof(a));
printf("%d\r\n", sizeof(a[0]));
printf("%d\r\n", sizeof(*a));
printf("%d\r\n", sizeof(&a[0][0]));
printf("%d\r\n", sizeof(a[0][0]));

output:
0x7ffd4b518aa0
0x7ffd4b518aa0
0x7ffd4b518aa0
0x7ffd4b518aa0
120
24
24
8
1
  • a=&a[0],a[0]=&a[0][0]
  • sizeof(a):表示整个二维数组的大小
  • sizeof(a[0]):表示第一行的一维数组的大小
  • sizeof(*a)*a=a[0],同sizeof(a[0])
  • sizeof(a[0][0]):表示a[0][0]这个元素的大小
  • sizeof(&a[0][0]):表示地址的大小
  • 一个指针32位机器上占4字节,在64位机器上占8字节
在这里插入图片描述
在这里插入图片描述
  • int a[5][5]:二维数组
  • char **p:二维指针
  • int (*p)[10]:一个指针,指向有10个元素的数组,也称指针
  • int* p[10]:一个数组,数组内每个元素都是指针

二维数组名不能传递给二级指针

二维数组二级指针没有直接关系。

代码语言:javascript
复制
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
int* *p = NULL;
p = (int**)a;       /* 不做强制类型转换会报错 */
  • p是二级指针,它首先是一个指针,指向一个int*
  • a是二维数组,它首先是一个指针,指向一个含有4个元素的int数组;
  • ap类型不相同,赋值操作需要强制类型转换
  • p=a[0]=&a[0][0],*p=a[0][0]=0
  • 访问**p的值报错,因为你访问了地址为0的空间,而这个空间你是没有权限访问的

数组名的含义

- 对于一维数组:

代码语言:javascript
复制
int arr[5]={1,2,3,4,5};
int (*p1)[5] = &arr;
/*下面是错误的*/
int (*p2)[5] = arr;

&arr是指整个数组的首地址,而arr是指数组首元素的首地址,虽然所表示的意义不同,但二者之间的值却是相同的。

赋值符号=号两边的数据类型必须是相同的,如果不同,则需要显示或隐式类型转换。在这里,p1p2 都是数组指针,指向的是整个数组。p1 这个定义的=号两边的数据类型完全一致,而p2 这个定义的=号两边的数据类型就不一致了(左边的类型是指向整个数组的指针,而右边的数据类型是指向单个字符的指针),因此会提示错误信息。

- 对于二维数组:

代码语言:javascript
复制
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};

a[0]该列的首地址&a[0][0]a整个数组的首地址a = a[0] = &a[0][0],值相同,但意义不同。

指针作为函数入参

当需要在函数内部改变传入的变量的值,就需要传这个变量的地址,对指针变量也一样。

一维指针

改变一维指针指向的值

当一维指针作为函数入参,且需要改变它的值,需要外部定义好、分配好内存。(或者函数return给它)

代码语言:javascript
复制
void test(char* ptr) 
{
    printf("ptr_1: %p\r\n", ptr);
    ptr = (char*)malloc(1);
    printf("ptr_2: %p\r\n", ptr);
    *ptr = '2';
}
int main(void) {
    char *ptr2;
    printf("ptr2_1: %p\r\n", ptr2);
    test(ptr2);
    printf("ptr2_2: %p\r\n", ptr2);
    return 0;
}

// 输出:
ptr2_1: (nil)
ptr_1: (nil)
ptr_2: 0x1b8b020
ptr2_2: (nil)

// 原因:
只是把指针变量指向的NULL值传给了函数,函数内又重新申请了内存malloc,外面的指针变量还是指向NULL。

改成

代码语言:javascript
复制
void test2(char* ptr) 
{
    printf("ptr_1: %p\r\n", ptr);
    *ptr = '2';
}

int main(void) {
    char *ptr2 = (char*)malloc(1);
    printf("ptr2_1: %p\r\n", ptr2);
    test2(ptr2);
    printf("ptr2_2: %p\r\n", ptr2);
    printf("test2: %s\r\n", ptr2);
    return 0;
}

// 输出:
ptr2_1: 0x1071020
ptr_1 : 0x1071020
ptr2_2: 0x1071020
test2: 2

// 原因:
传入了指针变量指向的地址,函数里设置了地址内存的值。外部的指针变量指向的还是之前的地址,但地址里已经有值了。
改变一维指针指向的地址

从上面的例子可以看出,单纯传递指针ptr,指针指向的地址0x1071020并不会变,变得只是地址里的值2。 如果要改变指针指向的地址,这时候就要取指针的地址作为函数入参了。

代码语言:javascript
复制
void test2(char** ptr) 
{
    printf("ptr_1: %p\r\n", *ptr);
    *ptr = (char*)malloc(1);
    printf("ptr_2: %p\r\n", *ptr);
}

int main(void) {
    char *ptr2 = (char*)malloc(1);
    printf("ptr2_1: %p\r\n", ptr2);
    test2(&ptr2);
    printf("ptr2_2: %p\r\n", ptr2);
    return 0;
}

// 输出:
ptr2_1: 0xc96020
ptr_1 : 0xc96020
ptr_2 : 0xc96040
ptr2_2: 0xc96040

ptr是一个指针,ptr本身有个地址如0x1,这个地址0x1里存放的是ptr指向的地址如0x2,这个地址0x2里存放的是真正的数据如'2'。函数传了ptr本身的地址0x1进去,在函数内部将它0x1指向的旧地址0x2指向了新的地址如0x3。跳出函数后,ptr本身的地址0x1没变,而它指向的地址变成了0x3

在这里插入图片描述
在这里插入图片描述

二维指针

函数指针

本质是一个指针变量,该指针指向这个函数。总结来说,函数指针就是指向函数的指针。

  • 函数指针有两个用途:调用函数做函数的参数
  • 声明格式: 类型说明符 (*函数名 ) (参数)
代码语言:javascript
复制
int (*func)(int, int);
// 静态的函数指针
int (*const funcPtr)();
// 指向的函数的返回值是常量
const int(*funcPtr)

把一个函数赋值给函数指针

代码语言:javascript
复制
int (*func)(int, int);
int add(int x, int y){
    return x+y;
}
func = add; // 函数标识符代表函数的首地址
// 或
func = &add;

调用函数指针

代码语言:javascript
复制
(*func)(1,2)
// 或
func(1,2)

函数指针数组

函数指针数组是一个其元素是函数指针的数组。那么也就是说,此数据结构是一个数组,且其元素是一个指向函数入口地址的指针

  • 首先说明是一个数组:数组名[]
  • 其次,要说明其元素的数据类型是指针:*数组名[]
  • 再次,要明确这每一个数组元素是指向函数入口地址的指针:函数返回值类型 (*数组名[])()
代码语言:javascript
复制
int (*op[2])(int, int);
op[0] = add1;
op[1] = add2;
op[0](1, 2);
代码语言:javascript
复制
// 先定义函数指针类型;在通过指针类型定义函数指针数组
// 定义一种P的类型,并定义这种类型为指向某种函数的指针,这种函数以(int, int)为参数并返回int类型*/
typedef int (*P)(int, int);
P a[2] = {0};
a[0] = add;
a[0](1,2);

typedef 普通用法

代码语言:javascript
复制
typedef <已有类型> <自定义别名>
如
typedef int INTE;
INTE a;
等价于
int a;

typedef 复杂用法

  • 为复杂的声明定义一个新的简单的别名。方法是:在原来的声明里逐步用别名替换一部分复杂声明,如此循环,把带变量名的部分留到最后替换,得到的就是原声明的最简化版。
代码语言:javascript
复制
int *(*a[5])(int, char*);
// 变量名为a,直接用一个新别名pFun替换a就可以了:
typedef int *(*pFun)(int, char*); 
// 最简化版:
pFun a[5];

void (*b[10]) (void (*)());
// 变量名为b,先替换右边部分括号里的,pFunParam为别名一
typedef void (*pFunParam)();
// 再替换左边的变量b,pFunx为别名二
typedef void (*pFunx)(pFunParam);
// 最简化版:
pFunx b[10];

doube(*)() (*e)[9]; 
// 变量名为e,先替换左边部分,pFuny为别名一
typedef double(*pFuny)();
// 再替换右边的变量e,pFunParamy为别名二
typedef pFuny (*pFunParamy)[9];
// 最简化版:
pFunParamy e;
  • 理解复杂声明可用的“右左法则”: 从变量名看起,先往右,再往左,碰到一个圆括号就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。
代码语言:javascript
复制
// 首先找到变量名func,外面有一对圆括号,
// 而且左边是一个*号,这说明func是一个指针;
// 然后跳出这个圆括号,先看右边,又遇到圆括号,
// 这说明 (*func)是一个函数,所以func是一个指向这类函数的指针,
// 即函数指针,这类函数具有int*类型的形参,返回值类型是int。
int (*func)(int *p);
  • 2个模式:
    • 函数指针 :type (*)(…)
    • 数组指针:type (*)[]
  • 不管什么时候,只要为指针声明 typedef,那么都要在最终的typedef名称中加一个const,以使得该指针本身是常量,而不是对象。

extern和包含头文件

  • extern是C语言中的一个关键字,一般用在变量名前函数名前,作用是用来说明“此变量/函数是在别处定义的,要在此处引用
  • 使用extern包含头文件来引用函数的区别: extern的引用方式比包含头文件要间接得多。extern的使用方法是直接了当的,想引用哪个函数就用extern声明哪个函数。这大概是kiss原则的一种体现。这样做的一个明显的好处是,会加速程序的编译(确切地说是预处理)的过程,节省时间。在大型C程序编译过程中,这种差异是非常明显的。
  • extern作用于函数名变量名时的区别: 函数声明时并没有使用 extern 关键字,这是因为,函数的定义有函数体,函数的声明没有函数体,编译器很容易区分定义和声明,所以对于函数声明来说,有没有extern 都是一样的 但是作用于变量名时extern关键字就不是可有可无的了,全局变量在外部使用声明时,extern关键词是必须的,如果变量无extern修饰且没有显式的初始化,就成为了变量的定义,因此此时必须加extern, (全局变量在不指定初值时会自动初始化为0)
  • 头文件中包含的都是函数声明,而不是函数定义
  • 最好不要头文件定义变量,例如全局变量

const

const*的前后关系,并且对于这个声明变量的文字叙述要从赋值号=前面开始,从右向左逐个翻译,const翻译为常量,*翻译为指针,如const char* name就翻译为name是一个指针,指向char,并且是const,使用汉语的构造句法就是name是一个指向const char的指针

代码语言:javascript
复制
const char* p = "123";
p ="abc";    // 正确
p[0] = '!';  // 错误 error: assignment of read-only location '*p'

char* const p = "123";
p[0] = '!';  // 正确
p ="abc";    // 错误 error: assignment of read-only variable 'p'

形式

含义

解释

char *p

可变字符指针

char const *p (=const char *p)

指向常量字符的可变指针

可以理解为:*p是const的,即它的值是const的,但它的指向是可变的

char* const p

指向可变字符的常量指针

可以理解为:p是const的,即它的指向是const的,但它的值是可变的

const char const *p

常量指针指向常量字符

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/09/25 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 二维函数和指针
    • 二维数组
      • 二维数组名不能传递给二级指针
        • 数组名的含义
          • 指针作为函数入参
            • 一维指针
            • 二维指针
        • 函数指针
          • 函数指针数组
          • typedef 普通用法
          • typedef 复杂用法
          • extern和包含头文件
          • const
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档