C语言部分复习笔记

1. 指针和数组

数组指针 和 指针数组

 int* p1[10]; // 指针数组
 int (*p2)[10]; // 数组指针

因为 [] 的优先级比 * 高,p先和 [] 结合说明p是一个数组,p先和*结合说明p是一个指针

括号保证p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个 指针,指向一个数组,叫数组指针。

arr和&arr的区别

arr代表数组首元素的地址,&arr代表整个数组的地址

 void test(int(*arr)[10], int size) // 这里arr也是整个数组的数组指针
 {
     for (int i = 0; i < size; ++i)
     {
         cout << ((int*)arr)[i] << " ";
     }
     cout << endl;
 }
 ​
 int main()
 {
     int arr[10] = { 0 };
     int(*p)[10] = &arr; // 数组指针需要指整个数组
     test(p, 10);
     return 0;
 }

二维数组传参

 
void test(int arr[3][5])//ok?
 {}
 void test(int arr[][])//ok? X
 {}
 void test(int arr[][5])//ok?
 {}
 // 总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
 // 因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。这样才方便运算。
 void test(int* arr)//ok?X
 {}
 void test(int* arr[5])//ok?
 {}
 void test(int(*arr)[5])//ok?arr是指向一个大小为5的一维数组
 {}
 void test(int** arr)//ok?
 {}
 int main()
 {
     int arr[3][5] = { 0 };
     test(arr);
 }

函数指针

保存函数的地址:函数指针

 #include <stdio.h>
 void test()
 {}
 ​
 int main()
 {
     printf("%p\n", test);
     printf("%p\n", &test); // 一样
     cout << typeid(test).name() << endl; // void __cdecl(void) 函数名
     cout << typeid(&test).name() << endl; // void (__cdecl*)(void) 函数指针
     void(*p1)(void) = test;
     void(*p2)(void) = &test; // 一样的
     return 0;
 }

函数指针数组

 typedef void(*handler)(void);
 ​
 int main()
 {
     handler arr[12] = { 0 };
     void(*arr1[12])(void)  = { 0 };
 }

const和指针

const修饰的指针变量:

  1. const位于*前的,表示指针指向的对象内容无法修改,p指向的空间内容(指向对象的内容)无法修改

  2. const位于*后面的,表示指针指向的位置无法修改,p的内容(保存的对象地址)无法修改

     const int* p = nullptr;
     int const* p = nullptr;
     int* const p = nullptr;

sizeof和指针,数组/strlen和指针,数组

sizeof是根据对象的类型判断大小,但是有一个特殊处理就是数组名,sizeof(数组名)

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小

  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址

  3. 除此之外所有的数组名都表示首元素的地址

  4. 但是参数数组也是一个特殊的存在,当数组作为参数进行传递的时候,数组其实退化成了指针

 //一维数组
 int a[] = {1,2,3,4};
 printf("%d\n",sizeof(a));       // 16
 printf("%d\n",sizeof(a+0));     // 4/8
 printf("%d\n",sizeof(*a));      // 4 
 printf("%d\n",sizeof(a+1));     // 4/8
 printf("%d\n",sizeof(a[1]));    // 4
 printf("%d\n",sizeof(&a));      // 4/8
 printf("%d\n",sizeof(*&a));     // 16(*和&抵消了)
 printf("%d\n",sizeof(&a+1));    // 4/8
 printf("%d\n",sizeof(&a[0]));   // 4/8
 printf("%d\n",sizeof(&a[0]+1)); // 4/8
 // 字符数组
 char arr[] = {'a','b','c','d','e','f'}; // 6个 系统不会给最后补0 ""这样赋值才行
 printf("%d\n", sizeof(arr));        // 6
 printf("%d\n", sizeof(arr+0));      // 4/8
 printf("%d\n", sizeof(*arr));       // 1
 printf("%d\n", sizeof(arr[1]));     // 1
 printf("%d\n", sizeof(&arr));       // 4/8
 printf("%d\n", sizeof(&arr+1));     // 4/8
 printf("%d\n", sizeof(&arr[0]+1));  // 4/8
 printf("%d\n", strlen(arr));        // 未知
 printf("%d\n", strlen(arr+0));      // 未知
 printf("%d\n", strlen(*arr));       // 错误
 printf("%d\n", strlen(arr[1]));     // 错误
 printf("%d\n", strlen(&arr));       // 报错
 printf("%d\n", strlen(&arr+1));     // 报错,因为&arr的类型char(*)[6]
 printf("%d\n", strlen(&arr[0]+1));  // 未知 优先级 [] > * > &
 char arr[] = "abcdef";  // 7个 最后补0
 printf("%d\n", sizeof(arr));        // 7
 printf("%d\n", sizeof(arr+0));      // 4/8
 printf("%d\n", sizeof(*arr));       // 1
 printf("%d\n", sizeof(arr[1]));     // 1
 printf("%d\n", sizeof(&arr));       // 4/8
 printf("%d\n", sizeof(&arr+1));     // 4/8
 printf("%d\n", sizeof(&arr[0]+1));  // 4/8
 printf("%d\n", strlen(arr));        // 6
 printf("%d\n", strlen(arr+0));      // 6
 printf("%d\n", strlen(*arr));       // 报错
 printf("%d\n", strlen(arr[1]));     // 报错
 printf("%d\n", strlen(&arr));       // 报错
 printf("%d\n", strlen(&arr+1));     // 报错
 printf("%d\n", strlen(&arr[0]+1));  // 5
 const char *p = "abcdef"; // 最后会补'\0'
 printf("%d\n", sizeof(p));          // 4/8
 printf("%d\n", sizeof(p+1));        // 4/8
 printf("%d\n", sizeof(*p));         // 1
 printf("%d\n", sizeof(p[0]));       // 1
 printf("%d\n", sizeof(&p));         // 4/8
 printf("%d\n", sizeof(&p+1));       // 4/8
 printf("%d\n", sizeof(&p[0]+1));    // 4/8
 printf("%d\n", strlen(p));          // 6
 printf("%d\n", strlen(p+1));        // 5
 printf("%d\n", strlen(*p));         // 报错
 printf("%d\n", strlen(p[0]));       // 报错
 printf("%d\n", strlen(&p));         // 报错
 printf("%d\n", strlen(&p+1));       // 报错
 printf("%d\n", strlen(&p[0]+1));    // 5
 ​
 //二维数组
 int a[3][4] = {0};
 printf("%d\n",sizeof(a));           // 48
 printf("%d\n",sizeof(a[0][0]));     // 4
 printf("%d\n",sizeof(a[0]));        // 16
 printf("%d\n",sizeof(a[0]+1));      // 4/8 (指针) a[0][1]
 // 这里a[0] 表示a的首个元素,因为sizeof的特殊所以被当成整个数组大小 +1 后这个特殊就没了
 printf("%d\n",sizeof(*(a[0]+1)));   // 4
 printf("%d\n",sizeof(a+1));         // 4/8
 printf("%d\n",sizeof(*(a+1)));      // 4/8X  16 a[1]
 printf("%d\n",sizeof(&a[0]+1));     // 4/8 
 printf("%d\n",sizeof(*(&a[0]+1)));  // 4X  16   a[1]
 printf("%d\n",sizeof(*a));          // 4/8X  16 a[0]
 printf("%d\n",sizeof(a[3]));        // 4/8X  16

总结:先看类型再判断

2. 库函数的模拟实现

memcpy

 void* memcpy(void* dest, const void* src, size_t num)
 {
     assert(dest && src);
     char* d = (char*)dest;
     const char* s = (const char*)src;
     while (num--)
     {
         *d++ = *s++;
     }
     return dest;
 }

注意:c++使用括号强转类型,生成的是临时变量,不能进行++

memmove

 void* memmove(void* dest, const void* src, size_t num)
     {
         assert(dest && src);
         char* d = static_cast<char*>(dest);
         const char* s = static_cast<const char*>(src);
         while (num--)
         {
             if (dest < src)
             {
                 *d++ = *s++;
             }
             else
             {
                 *((char*)(d + num)) = *(s + num); // 这里根据num的减少来推进
             }
         }
         return dest;
     }

strstr

 // 从目的字符串中找src字符串
 static char* strstr(const char* dest, const char* src)
 {
     assert(dest && src);
     const char* left = dest, * right = dest;
     const char* cur = src;
     while (true)
     {
         while (*left != '\0' && *left != *cur) left++;
         if (*left == '\0')
             break;
         // *left == *cur
         right = left;
         while (*right == *cur)
         {
             right++;
             cur++;
             if (*cur == '\0')
                 return const_cast<char*>(left);
         }
         cur = src; // cur 回执
         left++;
     }
     return nullptr;
 }

memset/strcmp

 void* memset(void* ptr, int val, size_t num)
 {
     assert(ptr);
     char* cur = static_cast<char*>(ptr);
     while (num--)
     {
         *cur++ = val;
     }
     return ptr;
 }
 int strcmp(const char* str1, const char* str2)
 {
     assert(str1 && str2);
     while (*str1 != '\0' && *str2 != '\0' && *str1++ == *str2++);
     //if (*str1 < *str2)
     //  return -1;
     //else if (*str1 > *str2)
     //  return 1;
     //else return 0;
     return *str1 - *str2;
 }

3. 自定义类型

内存对齐规则

  1. 第一个成员在与结构体变量偏移量为0的地址处。

  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处

  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍

  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

对齐数 = 编译器默认的一个对齐数(VS下是8) 与 该成员大小的较小值

联合体

联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为 联合至少得有能力保存最大的那个成员)

联合大小的计算:

联合的大小至少是最大成员的大小,当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍

4. 整形的存储规则

原码/反码/补码

计算机中的有符号数有三种表示方法,即原码、反码和补码

三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位三种表示方法各不相同

  • 原码:直接将二进制按照正负数的形式翻译成二进制就可以

  • 反码: 将原码的符号位不变,其他位依次按位取反就可以得到了

  • 补码: 反码+1就得到补码

正数的原、反、补码都相同

对于整形来说:数据存放内存中其实存放的是补码

大小端

大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中(高低)

小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中(高高)

如何判断:

 #include <stdio.h>
 int check_sys()
 {
     int i = 1;
     return (*(char *)&i);
 }
 int main()
 {
     int ret = check_sys();
     if(ret == 1)
     printf("小端\n");
     else
     rintf("大端\n");
     return 0;
 }
 //代码2
 int check_sys()
 {
     union
     {
     int i;
     char c;
     }un;
     un.i = 1;
     return un.c;
 }

5. 编译链接

#define 替换规则 在程序中扩展#define定义符号和宏时,需要涉及几个步骤:

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。

  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。

  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:

  1. 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归

  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索

也就是说"宏"只会被当成字符串,宏不会生效,这时#宏:把一个宏参数变成对应的字符串

 #include <stdio.h>
 ​
 #define PRINT1(FORMAT, VALUE) \
     printf("the value is "FORMAT"\n", VALUE)
 ​
 #define PRINT2(FORMAT, VALUE) \
     printf("the value of "#VALUE" is "FORMAT"\n", VALUE) // yes
     //printf("the value of ""VALUE"" is "FORMAT"\n", VALUE) // no
 ​
 int main()
 {
     char a = -1;
     signed char b = -1;
     unsigned char c = -1;
     printf("a=%d,b=%d,c=%d\n", a, b, c); // 对齐 -1 -1 255
     printf("file:%s\n line:%d\n time:%s\n", __FILE__, __LINE__, __TIME__);
     const char* p = "hello ""bit\n"; // 字符串合并
     printf("%s\n", p);
     PRINT1("%d", 10);
     PRINT2("%d", 10);
     return 0;
 }

可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符

 #define OFFSETOF(struct_name, member_name) \
     ((size_t)&(((struct_name*)0)->member_name))
 // 获取成员变量的偏移量

宏的优缺点:

优点

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序 的规模和速度方面更胜一筹。

  2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可 以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。(C++模板)

缺点

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度

  2. 宏是没法调试的。

  3. 宏由于类型无关,也就不够严谨

  4. 宏可能会带来运算符优先级的问题,导致程序容易出现错

#undef 于移除一个宏定义

 gcc -D ARRAY_SIZE=10 programe.c // 命令行宏定义

条件编译

 #if defined(OS_UNIX) 
     #ifdef OPTION1 
         unix_version_option1(); 
     #elif defined(OPTION2)
         unix_version_option2(); 
     #else
         unix_version_option3(); 
     #endif 
 #elif defined(OS_MSDOS) 
     #ifdef OPTION2 
         msdos_version_option2(); 
     #endif 
 #endif

编译链接过程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/763102.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

适用于高海拔地区的工业路由器产品

1、西藏背景 西藏&#xff0c;这个位于中国西南部的神秘之地&#xff0c;以其雄伟壮观、神奇瑰丽的自然风光和深厚的文化底蕴&#xff0c;被无数人视为心中的圣地。这里属于高原性气候&#xff0c;具有气温低、气压低&#xff0c;降水少&#xff0c;生态环境十分恶劣。西藏被誉…

Coze搭建《测测你的本命宠物》

前言 本文讲解如何从零开始&#xff0c;使用扣子平台去搭建《测测你的本命宠物》 《测测你的本命宠物》&#xff1a;测测你的本命宠物 - 扣子 AI Bot (coze.cn) 欢迎大家去体验一下&#xff01;&#xff01;&#xff01; 正文 接下来我们开始讲解制作这个bot的流程吧&#…

【后端面试题】【中间件】【NoSQL】MongoDB的优点和分片机制

为什么要用MongoDB 两个关键&#xff1a;灵活性和横向扩展能力 MongoDB是灵活的文档模型&#xff0c;也就是说&#xff0c;如果预计我的数据可以被一个稳定的模型来描述&#xff0c;会倾向于使用MySQL等关系型数据库。而一旦我认为我的数据模型会经常变动&#xff0c;比如我很…

Jenkins接口自动化项目的工程创建

jenkins的下载安装 jenkins下载的官网地址 https://www.jenkins.io/download/ java环境变量的配置下载 jenkins是用java语言编写的所以要配置java环境 需要安装java的JDK 推荐安装JDK17(https://blog.csdn.net/wochunyang/article/details/138520209) JDK17的下载地址 ht…

CS144 Lab3 TCPSender复盘

一.基础概念 1.TCPSender在TCPSocket中的地位与作用 Lab0中实现了基于内存模拟的流控制-字节流&#xff08;ByteStream&#xff09;&#xff0c;底层使用std::deque实现&#xff0c;根据最大容量Capacity进行容量控制。个人理解它相当于应用层的输入输出缓存区&#xff0c;用户…

什么是电航空插头插座连接器有什么作用

航空插头概述 定义与功能 航空插头&#xff0c;又称航空连接器&#xff0c;是一种专门用于航空领域的电连接器&#xff0c;因其最初在航空领域得到广泛应用而得名。航空插头的主要功能是实现电源或信号的连接&#xff0c;尤其适用于芯数较多、结构复杂的线束连接&#xff0c;…

QT在VS环境中使用,控件显示中文乱码解决方法

首先来看乱码显示的效果如下&#xff1a; 上图左侧显示内容为中文&#xff0c;控件对应代码如下&#xff1a; QLabel* UserNameLabel new QLabel(QString("用户名&#xff1a;")); QLabel* NameLabel new QLabel(tr("姓名&#xff1a;"));下面我们对QL…

实现高效全自动印刷:直线模组的智能化应用

目前&#xff0c;直线模组被广泛应用于移载、定位、喷涂、夹取、搬运、点胶、涂胶、封胶、移载、装配、检测测量、切割、上下料、钻孔、焊接、等自动化行业中&#xff0c;尤其是自动印刷行业&#xff0c;跟直线模组也是息息相关的。那么&#xff0c;如何利用直线模组实现全自动…

C++进阶 | [4.3] 红黑树

摘要&#xff1a;什么是红黑树&#xff0c;模拟实现红黑树 红黑树 &#xff0c;是一种 二叉搜索树 &#xff0c;但 在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是 Red 或 Black 。 通过对 任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树…

Web端登录页和注册页源码

前言&#xff1a;登录页面是前端开发中最常见的页面&#xff0c;下面是登录页面效果图和源代码&#xff0c;CV大法直接拿走。 1、登录页面 源代码&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8"><title>登录</ti…

超详细的 C++中的封装继承和多态的知识总结<2.多态>

引言 小伙伴们我们都知道了&#xff0c;什么是封装和继承&#xff0c;在有了这个的基础上我们接着来看什么是多态。多态从字面上意思我们就可以知道&#xff0c;大概就是一个函数的不同形态&#xff0c;而且&#xff0c;前边我们在学习函数重载的时候我们已经简单的了解了如何用…

企业源代码加密软件丨透明加密技术是什么

在一个繁忙的软件开发公司中&#xff0c;两位员工小李和小张正在讨论源代码安全的问题。 “小张&#xff0c;你有没有想过我们的源代码如果被泄露了怎么办&#xff1f;”小李担忧地问。 “是啊&#xff0c;这是个大问题。源代码是我们的核心竞争力&#xff0c;一旦泄露&#…

最短路算法三

图论三 20240624 算法实用主义&#xff0c;用到再学 1. 大纲&#xff1a; a. 最小生成树都是无向图 难在建图&#xff0c;不考原理&#xff0c;重点记思路&#xff08;是骨头&#xff09;&#xff0c;自己复述一遍&#xff0c;不能死记代码 血肉 类似最短路 prim&#xff08;…

web基础以及http协议

web基础介绍 web&#xff1a;就是我们所说的网页&#xff0c;打开网站展示的页面。&#xff08;全球广域网&#xff0c;万维网&#xff09; world wide web &#xff08;www&#xff09; 分布式图形信息系统 分布式&#xff1a;计算机系统或者是应用程序分布在多台独立的计算…

探索智慧校园人事系统,了解人事合同功能的核心优势

智慧校园人事系统中的人事合同管理功能&#xff0c;是一个高度集成且自动化的模块&#xff0c;专注于优化合同的全生命周期管理&#xff0c;从合同创建、审批、签署到存档及续签提醒&#xff0c;旨在提升人事管理工作的规范性与效率&#xff0c;同时保障学校的法律合规性。 在智…

微信小程序-插槽slot

一.插槽slot 在页面使用自定义组件的时候&#xff0c;如果在自定义组件里面写子组件&#xff0c;子组件的内容无法显示。 <custom01> <text slotslot-top>你好&#xff0c;上方组件</text> 你好&#xff0c;组件 <text slotslot-bottom>你好&#xf…

数据结构 - C/C++ - 栈

目录 结构特性 结构实现 结构容器 结构设计 顺序栈 链式栈 结构特性 栈(stack)是线性表的一种形式&#xff0c;限定仅在表的一端进行插入或者删除的操作。 栈顶 - 表中允许插入、删除的一端称为栈顶(top)&#xff0c;栈顶位置是可以发生变化的。 插入 - 进栈、入栈、压栈…

蒂升电梯职业性格和Verify认知能力SHL测评答题攻略及薪资待遇解密!

​一、蒂升电梯职业性格和认知能力测评考什么 您好&#xff01;蒂升电梯公司邀请您参加的OPQ职业性格测评和Verify认知能力测评是两种常见的评估工具&#xff0c;用于帮助了解个人的职场性格特点和认知能力。 OPQ职业性格测评 这是一种性格测试&#xff0c;通常用于评估个人在…

APP逆向 day8 JAVA基础3

一.前言 昨天我们讲了点java基础2.0&#xff0c;发现是又臭又长&#xff0c;今天就是java基础的最后一章&#xff0c;也就是最难的&#xff0c;面向对象。上一末尾也是提到了面向对象&#xff0c;但是面向对象是最重要的&#xff0c;怎么可能只有这么短呢&#xff1f;所以今天…

人工智能——常用数学基础之线代中的矩阵

1. 矩阵的本质&#xff1a; 矩阵本质上是一种数学结构&#xff0c;它由按照特定规则排列的数字组成&#xff0c;通常被表示为一个二维数组。矩阵可以用于描述一组数据&#xff0c;或者表示某种关系&#xff0c;比如线性变换。 在人工智能中&#xff0c;矩阵常被用来表示数据集…