网站主体备案信息查询,小程序代理商有哪些,dw软件主要做什么,株洲市网站关键词优化公司相关代码gitee自取#xff1a;
C语言学习日记: 加油努力 (gitee.com)
接上期#xff1a;
【C初阶】一、入门知识讲解 #xff08;C关键字、命名空间、C输入输出、缺省参数、函数重载#xff09;-CSDN博客 六 . 引用 #xff08;1#xff09;. 引用的概念和特性…
相关代码gitee自取
C语言学习日记: 加油努力 (gitee.com) 接上期
【C初阶】一、入门知识讲解 C关键字、命名空间、C输入输出、缺省参数、函数重载-CSDN博客 六 . 引用 1. 引用的概念和特性 引用的概念 在C语言中我们可以通过 指针 来实现对一个已存在的变量的“调用” 但指针可能相对会比较难理解一些 而在C中我们可以通过 引用 来实现对一个已存在的变量的“调用” 通过一个已存在的变量引用实体可以设置一个 引用变量 引用变量不是新定义一个变量而是给已存在变量引用实体取了一个别名 编译器不会为引用变量开辟内存空间 引用变量和被它引用的变量引用实体共用一块内存空间。 如孙悟空又叫弼马温又叫齐天大圣本质还是同一个人 生成引用变量的方式 引用变量类型 引用变量名(对象名) 引用实体 --------------------------------------------------------------------------------------------- 引用的特性 引用变量的类型必须和引用实体是同种类型的 引用变量在定义时必须初始化 一个变量可以有多个引用 如孙悟空被称为齐天大圣 而齐天大圣又可以被称为大圣 一个引用变量一旦引用了一个实体引用实体就不能再引用其它实体引用实体所以引用无法代替指针 因为引用变量和引用实体共用同块内存空间 所以引用变量的改变也会导致实体变量的改变 图示 2. 引用的使用场景 重点引用变量做函数的参数 将函数的参数设置为引用变量主函数调用函数时直接传变量名即可 变量名传到函数这边后函数中使用的就是该变量引用实体的引用变量 又因为引用变量和引用实体共用同一块内存空间 所以函数中引用变量形参的改变也会导致引用实体实参的改变 形参是实参的“别名”引用变量形参的改变就改变了实参 图示 传引用做参数的效率比传值做参的效率高 图示 -- 传值做参和传引用做参的效率对比 --------------------------------------------------------------------------------------------- 重点引用变量做返回值 将函数的返回值设置为引用变量函数执行完成后直接返回对应变量即可 编译器在返回时会自动生成该返回变量的引用变量并返回名字由编译器决定 主函数中也要通过对应类型的引用变量来接收该函数返回的引用变量 需要注意的是函数中被返回的变量应该是静态变量 这样可以保证该变量在函数结束后不会被销毁引用变量作为该变量的“别名” 共同使用同一块内存空间该空间不会被销毁才能保证引用变量的值是准确的 还有一点如果被返回的变量不是静态变量主函数第一次调用该函数并用对应引用变量接收时其接收的值是不确定的接收结果取决于编译器在函数调用后会不会销毁对应栈帧 如果会销毁则主函数中引用变量接收的值为随机值 如果不会销毁则接收的值为函数执行后的正常返回值 注VS编译器在函数调用结束后不会销毁对应栈帧 同时因为主函数中的引用变量和调用函数中的返回对象共用同一块内存空间 所以在之后再次调用该函数且不接收其返回值的情况下主函数中的引用变量即调用函数的返回对象的“别名”还会被改变 不接收函数返回值但“别名”还是被改变了这肯定不合适所以函数中被返回的变量应该是静态变量 正确使用例子 错误使用例子 传引用返回的效率比传值返回的效率高 图示 -- 传值返回和传引用返回的效率对比 传引用返回可以修改返回对象 图示 3. 常引用 一个变量的权限分为可读可写、只读、只写 一般创建一个变量后该变量的权限为可读可写 但如果对其进行const修饰附加常属性后其权限就变成了只读 即权限从可读可写变成了只读该变量的权限缩小了 而此时如果要对该变量进行引用那么引用变量也需要被const修饰 const引用变量 引用 const变量成为const变量的“别名”实现了权限的平移 引用变量实现权限的平移是被允许的而引用变量实现权限的缩小也是被允许的 即引用实体的权限是可读可写而引用变量的权限是只读 引用变量实现权限的平移和缩小都是被允许的唯独对权限的放大是不允许的 即引用实体的权限是只读被const修饰 而引用变量的权限却是可读可写未被const修饰 补充当需要进行类型转换时强制类型转换、隐式类型转换、类型截断 它都不是将右值转换为另一个类型然后就直接赋给左值的 而是会生成一个临时变量先将转换后的右值赋给临时变量再将此时的临时变量赋给左值。 所以当你进行引用操作时如果涉及到了类型转换引用实体会是类型转换过程中的临时变量即给临时变量起了个“别名”而不是对左值 图示 4. 引用和指针的区别 在语法层面概念层面上引用是定义一个变量的“别名”而指针是存储一个变量的地址 引用在定义时必须初始化而指针则没有要求 引用在初始化引用一个实体后就不能再引用其它实体了 而指针则可以在任何时候指向任何一个同类型实体 没有NULL引用空引用但有NULL指针空指针 引用和指针在sizeof中含义不同引用的结果为引用类型的大小 而指针的结果始终是地址空间所占字节个数32位平台下为4个字节 引用自加即引用的实体增加1 而指针自加即指针向后偏移一个类型的大小 没有多级引用但有多级指针 引用和指针访问实体的方式不同引用的话编译器会自己处理而指针则需要显示解引用* 引用比指针使用起来相对更安全因为引用只能指定一个实体 补充 -- 引用的底层实现 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 七 . 内联函数 1. 内联函数的概念 以inline修饰的函数叫做内联函数编译时C编译器会在调用函数的地方展开该内联函数 内联函数没有函数调用建立栈帧的开销所以内联函数可以提升程序运行的效率 内联函数克服了宏函数的缺点不用在意返回时括号的设置还可以进行调试同时还有宏函数的优点不用建立栈帧直接在调用函数的位置就会展开内联函数提高效率 现有一个宏函数 使用内联函数替代宏函数 2. 内联函数的特性 inline是一种以空间换时间的做法如果编译器将函数当成内联函数处理 在编译阶段会用函数体替换函数调用缺点可能会使目标文件变大 优势少了调用开销提高程序运行效率 inline对于编译器而言只是一个建议不同编译器关于inline实现机制可能不同一般建议对函数规模较小即函数不是很长具体没有准确的说法取决于编译器内部实现、不是递归、调用频繁的函数采用inline修饰否则编译器会忽略inline特性还是通过call指令通过函数地址调用函数 inline不建议声明和定义分离分离会导致链接错误 因为内联函数的声明在头文件中被展开会导致没有函数地址 在链接时call指令就找不到函数实现定义的地址了 图示 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 八 . auto关键字C11 1. auto简介 在早期C/C中auto的含义是使用auto修饰的变量是自动存储器的局部变量。 在C11中标准委员会赋予了auto全新的含义auto不再是一个存储类指示符而是作为一个新的类型指示符来指示编译器auto声明的变量由编译器在编译过程中推导得到定义一个auto类型的变量并对其进行初始化后auto变量的类型可以被自动推导出来 2. auto的使用和使用注意事项 auto的使用 auto变量可以自动推导类型初始化后auto变量的初始化对象可以是普通变量、指针变量和引用变量 auto变量除了有推导变量类型的作用还可以起到省略类型的定义的作用 因为C中有些变量的类型是很复杂的在理解了类型的情况下可以使用auto进行省略 图示 -- auto变量推导变量类型不常用 图示 -- auto变量省略类型的定义常用 --------------------------------------------------------------------------------------------- auto的使用注意事项 auto与指针、引用的结合使用 用auto声明指针类型时用auto或auto*都可以没有区别 但用auto声明引用类型时则必须加即auto 在同一行定义多个auto变量 当在同一行声明多个auto变量时这些auto变量的初始化变量必须是相同的类型 否则编译器将会报错因为编译器实际只对第一个初始化变量类型进行推导 然后用推导出来的类型定义其它的初始化变量 auto不能作为函数的参数最好也不要做返回值auto不能直接用来声明数组 为了避免与C98中的auto发生混淆C11只保留了auto作为类型指示符的用法 auto在实际中最常用的优势用法是在C11中提供的新式for循环中 还有lambda表达式等进行配合使用 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 九 . 基于范围的for循环(C11) 1. 范围for循环的语法 在C语言中对数组的遍历操作可以通过数组下标或者指针的形式。 但对于一个本身就有范围的集合来说还要程序员来说明循环的范围是有些多余的 还有可能因此造成错误。因此C11中引入了基于范围的for循环范围for循环后的括号通过冒号“ : ”分为两部分 第一部分为在范围内用于迭代的变量第二部分则表示被迭代的范围 即for在范围内用于迭代的变量 : 被迭代的范围 注 与普通循环类似可以用continue来结束本次循环也可以用break来跳出整个循环 图示 2. 范围for循环的使用条件 for循环迭代的范围必须是确定的 对于数组而言其范围就是数组中第一个元素和最后一个元素的范围 对于类而言应该提供begin和end的方法begin和end就是for循环迭代的范围 错误示范 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 十 . 指针空值nullptr(C11) 1. C98中的指针空值 在良好的C/C编程习惯中声明一个变量时最好给该变量一个合适的初始值否则可能会出现不可预料的错误 比如未初始化的指针如果一个指针没有合法的指向 我们一般会这样对其进行初始化 int* ptr1 NULL; 这里的NULL实际是一个宏在传统的C语言头文件stddef.h中NULL可能被定义为字面常量0或者被定义为无类型指针 (void*) 的常量0 在使用空值的指针时都不可避免地会遇到一些麻烦当你想把NULL定义为无类型指针 (void*) 的常量时NULL可能被编译器定义为常量0 因此与程序的初衷相悖 在C98版本中字面常量0既可以是一个整型数字 也可以是无类型的指针(void*)常量但是编译器默认情况下会将其看成是一个整型常量, 如果要将其按照指针方式来使用还必须对其进行强转为 (void*)0 显得头文件中的定义有些多余 所以在C11版本中有了一个新关键字nullptr来专门表示指针空值使用nullptr表示指针空值时不需要包含头文件因为nullptr是一个关键字 在C11中sizeof(nullptr) 与 sizeof((void*) 0) 所占的字节数相同 为了提高代码的健全性在未来的编程中表示指针空值时建议最好使用nullptr 图示 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C入门知识讲解 -- 对应代码 Test.cpp文件 #define _CRT_SECURE_NO_WARNINGS 1#include stdio.h
#include stdlib.h/*
* //C 兼容 C
* //C语言版“hello world”
* int main()
* {
* printf(hello world\n);
*
* return 0;
* }
*///CPP
//兼容C语言
//面向对象
//泛型
//C的不足的弥补/*
//C版“hello world”
#include iostream//第一个特性命名空间
using namespace std;int main()
{//第二个特性IO流cout hello world endl;return 0;
}
*///用C语言解释CPP的命名空间/*
* C语言不足命名冲突
* 1、我们写的变量名和库中的函数冲突
* 2、两个程序员定义了相同的变量名导致冲突
*///int rand 0;
rand变量 和 stdlib.h头文件
中的rand()函数冲突了
//
所以可以使用namespace命名空间解决该问题
//namespace my
//{
// //命名空间中定义属于自己的rand变量
// int rand 0;
//
// //命名空间中还可以再嵌套子命名空间
// namespace my1
// {
// int rand 1;
// }
//
// //命名空间中定义属于自己的Add函数
// int Add(int left, int right)
// {
// return left right;
// }
//
// //命名空间中定义属于自己的Node结构体
// //C中叫做类
// struct Node
// {
// struct Node* next;
// int val;
// };
//}
使用命名空间时类似结构体但{}后不用加;
//
//namespace your
//{
// int rand 0;
//}
my命名空间中的rand和
your命名空间中的rand独立
//
//
//int main()
//{
// printf(hello world\n);
//
// printf(%p\n, rand);
// //使用namespace命名空间后
// //这里默认调用的就是全局中的rand
// //即stdlib.h头文件中的rand函数(指针)
// //而不会找命名空间中的rand
//
// //如果要使用my命名空间中的rand变量
// //需要使用到
// // :: -- 域作用限定符
// printf(%d\n, my::rand);
//
// //通过域作用限定符来使用
// //my命名空间中的Add函数
// my::Add(1, 2);
//
// //通过域作用限定符来使用
// //my命名空间中的Node结构体类
// struct my::Node node;
// //注命名空间是指定在结构体名称的位置
// // 而不是指定在struct的前面
//
// //访问嵌套的子命名空间my1
// printf(%d\n, my::my1::rand);
//
// return 0;
//}#include stdio.h
#include stdlib.h/*
* //全局变量rand
* int rand 0;
*/namespace ggdpz //俺滴博客名
{//命名空间中可以定义变量、、函数、类型……int rand 10; //变量int Add(int left, int right) //函数{return left right;}struct Node //类型结构体{struct Node* next;int val;};//命名空间中嵌套子命名空间namespace ggdpz1{int rand 20;}
}//同一程序中的同名命名空间
namespace ggdpz
{int rand1 30;
}使用方式二对命名空间进行展开
using namespace ggdpz;
//
使用方式三对命名空间的某个成员进行展开
//using ggdpz::rand1;
//
//int main()
//{
// //printf(%d\n,rand);
// //rand变量 和 stdlib.h头文件
// //中的rand()函数冲突了
//
// /*
// * 编译后报错
// * error C2365“rand”重定义以前的定义是“函数”
// *
// * C语言没有办法解决类似这样的命名冲突问题
// * 所以C中提出了namespace命名空间来解决该问题
// */
//
// //通过方式一使用命名空间内容
// //printf(通过方式一%d\n, ggdpz::ggdpz1::rand);
//
// //通过方式二使用命名空间内容
// //printf(通过方式二%d\n, rand1);
//
// //通过方式三使用命名空间内容
// printf(通过方式三%d\n, rand1);
//
// printf(未通过方式三%d\n, ggdpz::rand);
//
//
// return 0;
//}//
//#include Stack.h
//
展开命名空间更加方便自己测试
//using namespace ggdpz;
展开后就默认使用该命名空间中的内容
//
///*
//* using namespace std;
//* //stdC官方库定义的命名空间
//* //展开后库里的东西就可以顺便用了
//* //工程项目中不要展开std容易命名冲突
//* //如果是日常练习为了方便可以展开
//*/
//
//int main()
//{
// //获取命名空间中的栈类型
// //ggdpz::ST s; //展开命名空间前
// ST s; //展开命名空间后
// //先到全局中找ST再到第一个展开的命名空间中找ST
// //然后到第二个展开的命名空间中找ST……
// //找到了就停止“从上到下找”未找到则报错
//
// //获取命名空间中的栈初始化函数
// //ggdpz::StackInit(s); //展开命名空间前
// StackInit(s); //展开命名空间后
//
// //获取命名空间中的出栈函数
// //展开命名空间前
// //ggdpz::StackPush(s,1);
// //ggdpz::StackPush(s,2);
// //ggdpz::StackPush(s,3);
//
// //获取命名空间中的出栈函数
// //展开命名空间后
// StackPush(s, 1);
// StackPush(s, 2);
// StackPush(s, 3);
//
// return 0;
//}方式一指定命名空间
方式二展开命名空间谨慎使用
//
//#include iostream
C经常会包含一个头文件
iostream IO流
C中的头文件一般不写…….h比较老的编译器才写
//
///*
//* 每次指定命名空间很不方便
//* 直接展开命名空间全部暴露又有冲突风险
//* 这时使用 指定展开 就可以解决问题
//*/
//using std::cout; //指定展开std中的cout对象
//using std::endl; //指定展开std中的endl对象
//
//int main()
//{
// std::cout hello world;
// // -- 流插入运算符 C
// //在C语言中是位运算符
// //在C中多了个身份流插入运算符
//
// //cout -- console out -- 控制台输出 -- 流插入
// //console可以理解为控制台/终端/黑窗口是一个对象
// //是std库中的内容
//
// //所以 std::cout hello world;
// //其实可以理解为将“hello world”这个字符串
// //流进std::cout这个终端中
//
// int a 10;
// double b 11.11;
//
// //使用cout这个对象可以自动识别变量的类型
// //即不用像C语言中打印时需要加上%d、%p……
// //直接让该变量流进cout对象即可
//
// std::cout a;
// std::cout b;
// //自动识别输出变量的类型
//
// //换行
// std::cout helloc world\n;
//
// std::cout a \n; //\n -- 字符串换行
// std::cout a \n; //\n -- 字符换行
// std::cout b \n;
// std::cout b \n;
//
// //也可以合并写成
// std::cout a \n b \n;
//
// //C中通常将换行符\n写成std::endl
// //endl - endline
// std::cout a std::endl b std::endl;
//
// //指定展开后可写为又不会有命名冲突风险
// cout hello world;
// cout a;
// cout b;
// cout helloc world\n;
// cout a \n; //\n -- 字符串换行
// cout a \n; //\n -- 字符换行
// cout b \n;
// cout b \n;
// cout a \n b \n;
// cout a std::endl b std::endl;
//
// //cin -- console in -- 控制台输入 -- 流提取
// //console可以理解为控制台/终端/黑窗口是一个对象
// //是std库中的内容
// std::cin a b;
// //让你在控制台上输入的数据分别流入a和b这两个变量中
//
// // -- 流提取运算符 C
// //在C语言中是位运算符
// //在C中多了个身份流提取运算符
//
// return 0;
//}包含IO流头文件
//#include iostream
//
指定展开命名空间成员
//using std::cout; //指定展开标准输出对象控制台
//using std::cin; //指定展开标准输入对象键盘
//using std::endl; //指定展开C换行符号
//
//int main()
//{
// int a 10; //整型变量
// double b 3.14; //浮点型变量
//
// cout 使用cout打印当前a和b endl;
//
// //使用cout进行输出
// cout a endl b endl;
// /*
// * 通过cout标准输出对象和流插入运算符进行输出打印
// *
// * 先将a这个变量流进std::cout这个控制台中打印
// * 再进行endl换行再将b这个变量
// * 流进std::cout这个控制台中打印再换行。
// *
// * 即使 a变量 和 b变量 的类型不同也能打印
// * C的输入和输出可以自动识别变量类型
// */
//
// cout 使用cin分别输入数据到a和b endl;
//
// //使用cin进行输入
// cin a b;
// /*
// * 通过cin标准输入对象和流提取运算符对数据进行输入
// *
// * 让你在控制台上输入的数据分别流入a和b这两个变量中
// *
// * 即使 a变量 和 b变量 的类型不同也能输入
// * C的输入和输出可以自动识别变量类型
// */
//
// cout 输入后再使用cout进行输出打印 endl;
//
// cout a endl b endl;
//
// return 0;
//}/*
//函数的 缺省参数默认参数
void Func(int a 0)
//这里的 “0” 就是缺省参数默认参数
//如果没有接收对应的参数则缺省参数就是该参数的值
{cout a endl;
}int main()
{Func(); //不传参数的话0就是参数a的默认值Func(10);//接收参数的话10该参数就是a的默认值return 0;
}
*/缺省参数
//
包含IO流头文件
//#include iostream
//
展开std命名空间为了方便
//using namespace std;
//全缺省参数
//void Func(int a 10, int b 20, int c 30)
//{
// cout a a endl; //打印ac
// cout b b endl; //打印bc
// cout c c endl; //打印cc
//}
//
//
//半缺省参数
///*
//* 半缺省的情况下
//* 到了要设置缺省参数时
//* 不能“中断着给”只能“连续给”
//* 即半缺省只能“从右往左给”必须连续给或者只给一个
//*
//* 例如
//* 假设有三个参数给缺省参数时不能是给不给给
//* 但可以“从左往右连续给”即不给给给
//* 或只给一个
//*给不给不给、不给给不给不给不给给
//* 注
//* 不能是“从左往右连续给”因为如果是给给不给
//* 然后调用函数时是Fun12这时就会有歧义
//* 不知道这两个参数是给该函数的哪个参数的
//* 而如果是“从右往左连续给”Fun12中
//* 1就对应第一个参数2就对应第二个参数
//*
//* 缺省参数不能在 “声明” 和 “实现” 中都给
//* 都给的话如果“声明”和“实现”中给的缺省参数不一样
//* 编译器就不知道要用哪个缺省参数
//* 如果只在当前文件的“实现”中给缺省参数的话
//* 到时候又有个新文件调用“实现”
//* 新文件中的“实现”又需要重新给缺省参数
//* 所以最好还是只在“声明”中给到函数缺省参数
//*/
//void Func(int a, int b 20, int c 30)
//{
// cout a a endl; //打印ac
// cout b b endl; //打印bc
// cout c c endl; //打印cc
//}
//
主函数
//int main()
//{
// //有了缺省参数后
// //可以调整参数的各种形式来调用该函数
// Func();
// Func(1);
// Func(1, 2);
// Func(1, 2, 3);
//
// //Func(, 2, ); //不能“跳着(间隔)”设置缺省参数
//
// return 0;
//}
//
//
//
//
//
函数重载
函数名相同但参数情况不同类型不同、个数不同
注类型不同又包括顺序不同
//
参数情况不同类型不同
//int Add(int left, int right) //整型相加
//{
// cout int Add(int left, int right) endl;
//
// return left right;
//}
//
//double Add(double left, double right) //浮点型相加
//{
// cout double Add(double left, double right) endl;
//
// return left right;
//}
//
参数情况不同个数不同
//void f()
//{
// cout f() endl;
//}
//
//void f(int a)
//{
// cout f(int a) endl;
//}
//
重载函数和缺省参数结合
//void f(int a 0)
//{
// cout f(int a) endl;
//}
///*
//* 结合后能够构成重载
//* 但这时如果不传参数的话就会存在二义性歧义
//* 不知道调用f()函数还是f(int a 0)函数
//*/
//
//
//int main()
//{
// //调用整型相加的Add函数
// Add(1, 2);
// //调用浮点型相加的Add函数
// Add(1.1, 2.2);
//
// Add(1, 2.2);
// /*
// * 已经定义了一个整型的Add()函数
// * 还有一个浮点型的Add()函数了
// * 这时如果使用Add()函数且一个参数为整数另一个参数为浮点数
// * 这时会报错如果整型和浮点型Add()函数我们只实现了其中一个
// * 其实是可以可以通过的
// * 因为可以将其中一个参数的类型隐式转换为另一种类型
// * 而我们即定义了整型Add()函数又定义了浮点型Add()函数
// * 就不清楚要隐式转换为何种类型了有歧义
// */
//
// //调用参数个数为0的f函数
// f();
// //调用参数个数为1的f函数
// f(1);
//
//
// //系统会根据你参数的情况来匹配对应的重名函数
//
// return 0;
//}//包含Func函数头文件
//#include Func.h
//
//int main()
//{
// //分别调用重载函数
// Func(1, x);
// Func(y, 2);
//
// return 0;
//}/*
* 为什么C语言不支持函数重载而C支持函数重载
*
* 需要先复习一下编译链接过程
*
* 有三个文件
* Func.h Func.cpp Test.cpp
*
* 执行时需要进行以下过程
* 预处理 - 编译 - 汇编 - 链接
*
* 一
* 预处理头文件展开(主) / 宏替换 / 条件编译 / 去除注释
* 在Func.cpp文件和Test.cpp文件中因为包含了Func.h头文件
* 在预处理时会对头文件进行展开之后会生成预处理文件Func.i
* 在该文件中就会有Func函数的声明和实现Func.i函数声明和定义
* 而Test.cpp文件同理会生成一个Test.i预处理文件
* 该文件包含函数的声明和调用Test.i函数的声明和实际调用
*
* 二
* 编译检查语法 / 生成汇编代码
* 编译时会生成汇编代码文件.s文件,
* 即 Func.s文件 和 Test.s 文件
* 分别由 Func.i 和 Test.i 生成
* Func.s文件中存放了重载函数两个f()函数对应的汇编代码
* 而Test.s文件中则存放了主(main)函数的汇编代码,
* 包括其调用的两个f()函数重载函数的汇编代码
* 而要调用这两个函数需要用到汇编语言中的call指令来获取函数地址
* 但是在编译阶段因为Test.cpp中只包含了Func.h头文件只有函数声明
* 没有函数实现(定义)所以call指令还无法获得对应的函数地址
* 而编译器会判断调用的函数和头文件中函数是否匹配匹配的话
* 即使call指令还没找到函数地址也可以先让其通过为了实现多文件项目
*
* 三
* 汇编将汇编代码文件中的代码转换为二进制的机器码CPU能“读懂”的代码
* 汇编后会生成 Func.o 和 Test.o 目标文件
* 分别由 Func.s 和 Test.s 生成
*
* 四
* 链接将目标文件合并到一起链接一些没有确定的函数地址等等
* 将汇编中的 Func.o 和 Test.o 文件合并为 a.out 文件默认情况下
* 合并后的a.out文件中 Test.o 中之前call函数未找到的函数地址
* 就能在 Func.o 中的函数实现(定义) 中找到了
*//*
* 所以我们可以知道在“编译”时call指令未找到调用函数地址
* 等到了“链接”时合并文件后才能找到。
*
* 在C语言中没有重载函数即函数名唯一的情况下
* 要找函数地址只需要通过唯一的函数名即可找到
* 在“链接”步骤中通过函数名在.o目标文件的符号表进行查找地址
* 所以C语言中如果有重载函数函数名不唯一就无法“链接”到函数地址
*
* 而在C有重载函数即函数名不唯一的情况下不同环境实现方式不同
* (Linux的g中)C有一个函数可以通过函数名和参数情况修饰出一个名字
* 函数名相同但参数情况不同修饰出的名字不同
* 再通过修饰出的名字来查找对应的函数地址。
* 修饰名字构成_Z 函数名字符个数 函数名 各参数首字母
* 如Func(int a, double b) 修饰后名字为_Z4Funcid
*//*
* 引用给已存在的变量取一个别名
* 编译器不会为引用变量开辟内存空间
* 它和它引用的变量共用同一块内存空间
*/包含IO流头文件并展开std命名空间
//#include iostream
//using namespace std;
//
//int main()
//{
// //创建一个变量a假设a为“孙悟空”
// int a 1;
//
// //正常情况可以创建另一个变量来存储a的值
// //通过指针表示也可以
// int b a;
//
// //使用引用变量表示a“孙悟空又叫弼马温 -- c”
// int c a;
// /*
// * 相当于给已存在的a变量取了一个别名
// * 引用变量c和变量a共用同一块内存空间
// * a就是cc就是a只是叫法不同
// */
//
// b; //变量b不和变量a共用内存空间
// //所以b不会影响到变量a
//
// c--; //引用变量c和变量a共用内存空间
// //所以c--会影响到变量a的值a也会--
//
// //取了一个外号后即有了一个引用变量后
// //还可以再取多个外号即可以取多个引用变量
// //“孙悟空除了叫弼马温还叫齐天大圣 -- d”
// int d a;
//
// //也可以对“外号”再取一个“外号”
// // “齐天大圣又可以简称大圣 -- e”
// //即对引用变量也可以设置一个引用变量
// int e c;
//
// /*
// * a-孙悟空 c-弼马温 d-齐天大圣 e-大圣
// * c、d、e都是引用变量都是对变量a的引用
// * 所以a、c、d、e都共用同一块内存空间
// * 即这4个变量的地址都相同
// */
// //打印这四个函数的地址
// cout 变量a的地址 a endl;
// cout 其引用变量c的地址 c endl;
// cout 其引用变量d的地址 d endl;
// cout 其引用变量e的地址 e endl;
//
// return 0;
//}//引用的意义之一做参数C语言指针
//void Swap(int* left, int* right)
//{
// int temp *left;
// *left *right;
// *right temp;
//} //两值交换函数通过指针实现
//
C引用
定义另一个Swap函数构成函数重载
//void Swap(int left, int right)
//{
// int temp left;
// left right;
// right temp;
//} //两值交换函数通过引用变量实现//int main()
//{
// int a 1;
// int b 2;
//
// //通过函数调换两值
// Swap(a, b); //传地址实现交换
//
// Swap(a, b); //正常传变量实现交换
// /*
// * 因为重载函数Swap(int left, int right)
// * 参数 left 和 right 都是引用变量
// * 主函数正常传递变量a和b来调用该函数
// * 该Swap函数接收后
// * int left 就是 int a 的 引用变量
// * int right 就是 int b 的 引用变量
// * 所以 left 和 a 共用同一地址right 和 b 共用同一地址
// * 这样一来形参left和right的改变就能分别改变实参a和b
// * 所以不会导致在调用函数时形参改变而实参不改变的情况
// */
//
// return 0;
//}/*
* C中 引用 可以替代 指针 吗
* 答案是不行C的引用相较java的比较特殊
* java中的引用可以改变指向而C的不行
* 如
* b是a的引用变量“别称”还有个c变量
* Java中就可以将引用变量b改变指向c变量成为c变量的“别称”
* 而C中就不行了b只能是a的引用变量“别称”
*
* C中在数据结构的链表中就无法用引用来代替指针
* 当要插入一个结点时需要改变next指针的指向
* 而不能用引用来实现相同操作因为引用无法改变指向
*/
//int main()
//{
// //定义一个变量a作为引用实体
// int a 0;
//
// //1进行“引用”“引用”时必须初始化
// //因为要“起别名”首先得有引用实体指明对谁进行“引用”
// int b;
//
// //引用 变量a “生成” 引用变量c
// int c a;
//
// //再生成一个普通变量d
// int d 1;
//
// //2C的引用无法改变指向
// //如果此时将普通变量d赋给a的“别名”c
// c d;
// //只会将变量d的值赋给a的“别名”c
// //二不会改变“别名”(引用变量)的指向
// //c还是a的“别名”不会变成d的“别名”
//
// //3一个对象可以有多个“别名”可以为“别名”再取一个“别名”
// int e a; //a现在有c和e两个“别名”
// int f e; //“别名”e现在又有一个“别名”f
//
// return 0;
//}/*
* phead是结点的地址*phead可以改变结点
* 但尾插可能会改变phead指针本身
* 这时就要定义phead的二级指针pphead
* *pphead就可以改变phead了
* 所以也需要这个二级指针
*/传值、传引用效率比较
//#include time.h
//#include iostream
//using namespace std;
//
//
创建一个“大对象”A对象中有一个数组(10000个元素)
//struct A { int a[10000]; };
//
“大对象传参” -- 传值方式
//void TestFunc1(A aa) {}
传值方式需要进行临时拷贝实参多大就要拷贝多大
//
//
“大对象传参” -- 传引用对象方式
//void TestFunc2(A aa) {}
传“别名”就好传“别名”的类型大小的数据
//
//void TestRefAndValue()
//{
// //创建一个“大对象”变量
// A a;
//
// // 以值作为函数参数
// size_t begin1 clock();
// for (size_t i 0; i 10000; i)
// TestFunc1(a);
// size_t end1 clock();
//
//
// // 以引用作为函数参数
// size_t begin2 clock();
// for (size_t i 0; i 10000; i)
// TestFunc2(a);
// size_t end2 clock();
//
// // 分别计算两个函数运行结束后的时间
// cout 传值方式TestFunc1(A) - time : end1 - begin1 endl;
// cout 传引用(变量)方式TestFunc2(A) - time : end2 - begin2 endl;
//}//int main()
//{
// //传值、传引用效率比较
// TestRefAndValue();
//
// return 0;
//}/*
* 使用引用做函数参数还可以提高效率
*
* 如果参数是一个很大的对象的话
* 使用 传值 就需要将该“大对象”整个拷贝给函数形参
* 使用 指针 则传地址四个字节就好
* 使用 引用 的话则传“别名”就好传“别名”的类型大小的数据
*///引用的意义之二做返回值函数的传值返回int Count()
//int Count()
//{
// //创建变量n
// int n 0;
//
// n;
//
// //传值返回n
// return n;
// /*
// * Count函数结束n就会销毁
// * 所以n在传值返回时
// * 实际返回的是n的拷贝如果n比较小
// * n就会被拷贝到寄存器中
// * 这个拷贝就会作为Count函数的返回值
// */
//}函数的传引用返回int Count()
//int Count()
//{
// //创建变量n
// int n 0;
//
// n;
//
// //传值返回n
// return n;
// /*
// * 引用返回的话会返回n的“别名”引用变量
// * 引用变量的名字由编译器决定假设叫做tmp
// * 引用变量tmp和n共用同一地址所以返回“别名”tmp时
// * 所以就相当于返回了n
// */
//}
//
//
//int main()
//{
// int ret Count();
// /*
// * Count()函数是传引用返回的情况下
// * 最后返回的“别名”的值即ret接收的值是不确定的
// * ret接收的值取决于Count函数结束时的栈帧会不会被销毁
// * 如果会被销毁则ret会接收随机值如果不会被销毁则ret接收1
// * 在VS编译器下Count函数结束后栈帧不会被销毁所以ret会接收1
// */
//
// //打印Count函数的返回值
// cout ret endl;
//
// return 0;
//}加法函数使用传引用返回错误示范
//int Add(int a, int b)
//{
// int c a b;
//
// //返回两值相加结果
// return c;
// //使用传引用返回
// //编译器会自动生成一个对应的int类型的引用变量返回
//}错误示范
//int main()
//{
// //调用加法函数并使用引用变量接收
// int ret Add(1, 2);
//
// //打印相加结果
// cout 第一次调用Add(12)时 ret is : ret endl;
// /*
// * 第一次可能会打印3也可能打印随机值
// * 取决于编译器是否会清理Count函数的栈帧
// * VS编译器在调用完函数后不会清理函数栈帧
// * 所以此时会打印3
// */
//
// Add(3, 4); //换个值再次调用
// //但这次不用引用变量接收了
//
// //再次打印之前的相加结果
// cout 第二次调用Add(1, 2)但未接收返回值时 ret is : ret endl;
// /*
// * 这次打印会打印7
// * 明明第二调用函数时没有将结果赋给ret
// * 但ret还是被改变了
// * 本质原因ret引用的是函数中临时变量c
// * 即ret是Add()函数中临时变量c的引用变量
// * 即使函数调用后被销毁了
// * ret依旧和临时变量c共用同一空间
// * 所以之后再调用函数时即使ret不接受返回值
// * ret依旧能到达c的值
// */
//
// //所以出了函数作用域后返回对象就销毁了
// //不能使用 引用 返回该对象。否则结果是不确定的
// //结果取决于编译器是否会在函数调用后销毁其栈帧
//
// return 0;
//}//加法函数使用传引用返回正确示范
//int Add(int a, int b)
//{
// static int c a b;
// /*
// * 将之后会被返回的对象设置为静态变量
// * 这时c就不在Add()函数的栈帧中了而是在静态区中
// * 防止函数调用完后被一起销毁掉
// * 所以接收该返回值的引用变量就不是一个销毁空间的别名了
// * 避免出现调用完该函数后接收的返回值结果不确定的情况
// *
// * 注这样写变量c的话该代码只会被调用一次
// */
//
// /*
// * static int c;
// * c a b;
// * 如果是这样写该代码的话
// * c a b; 这条代码就会被反复调用
// * 这样之后c的“别名”也会被改变
// *即使返回值不被接收的情况下
// */
//
// //返回两值相加结果
// return c;
// //使用传引用返回
// //编译器会自动生成一个对应的int类型的引用变量返回
//}正确示范
//int main()
//{
// //调用加法函数并使用引用变量接收
// int ret Add(1, 2);
//
// //打印相加结果
// cout 第一次调用Add(12)时 ret is : ret endl;
// /*
// * 函数中将返回对象设置为静态变量后
// *局部的静态变量只会被初始化一次后面再调用就不会再使用它了
// * ret正常接收一个不会被销毁的变量c的引用变量
// * ret是Add()函数中c的“别名”所以ret会正常打印3
// */
//
// Add(3, 4); //换个值再次调用
// //但这次不用引用变量接收了
//
// //再次打印之前的相加结果
// cout 第二次调用Add(1, 2)但未接收返回值时 ret is : ret endl;
// /*
// * 因为函数中被返回的变量为静态变量
// * 只会被执行一次所以这次调用函数但不接收的情况下
// * ret不会改变这才是传引用的正确使用方法
// */
//
// return 0;
//}//传值和传引用返回值的效率#include time.h
#include iostream
using namespace std;struct A { int a[10000]; }; //“大对象”A a; //“大对象”全局变量不会被销毁// 值返回
A TestFunc1() { return a; }// 引用返回
A TestFunc2() { return a; }//效率测试函数
void TestReturnByRefOrValue()
{// 以值作为函数的返回值类型size_t begin1 clock();for (size_t i 0; i 100000; i)TestFunc1();size_t end1 clock();// 以引用作为函数的返回值类型size_t begin2 clock();for (size_t i 0; i 100000; i)TestFunc2();size_t end2 clock();// 计算两个函数运算完成之后的时间cout 以值作为函数的返回值类型TestFunc1 time: end1 - begin1 endl;cout 以引用作为函数的返回值类型TestFunc2 time: end2 - begin2 endl;
}//int main()
//{
// TestReturnByRefOrValue();
//
// return 0;
//}//传引用对比传值在传参和做返回值时传引用的效率都会比较高//“引用”做返回值可以修改返回对象
#include iostream
#include assert.h
using namespace std;typedef struct SeqList
{int a[100];int size;
}SL;void SLModify(SL* ps, int pos, int x)
{//...assert(ps);assert(pos ps-size);ps-a[pos] x;
}//通过传引用返回返回对应顺序表中pos下标位置的值的“别名”
int SLat(SL* ps, int pos)
{assert(ps);assert(pos ps-size);return ps-a[pos];
}//int main()
//{
// SL s;
//
// //初始化……
//
// //顺序表每个位置的值都
// for (size_t i 0; i s.size; i)
// {
// SLat(s, i);
// //“引用”返回的是顺序表中pos位置那个字符的“别名”
// //对“别名”进行修改就是对顺序表中pos位置那个字符的修改
// }
//
// return 0;
//}/*
* 引用的价值是做参数、做返回值
* 做参数时能够让形参的修改影响实参提高效率
* 做返回值时可以修改返回值返回对象提高效率
*/常引用
//int main()
//{
// const int a 10; //const赋予常属性只读
// int b a;
// //a赋予常属性后无法被引用因为常量无法修改
// //这时对a进行引用会导致a的“权限放大”
// //从 只读 变成了 可读可写 “权限的放大”
//
// int b a; //这是可以的
// //这里是把a的值拷贝赋予bb和a不共用同一空间
// //b是一个新变量b的改变不会改变a
// //和“别名”不同“别名”的改变会影响a
//
// //权限不允许放大但允许“权限的平移”
// const int b a; //也赋予引用变量常属性即可
// //和 a 一样 只读 “权限的平移”
//
// //权限不仅允许“权限的平移”还允许“权限的缩小”
// int c 20; //可读可写
//
// const int d c;
// //从 可读可写 变成了 只读“权限的缩小”
//
// //还可以给常量如数字取“别名”
// const int e 10; //给常量取“别名”
// //10是常量只读所以要赋予引用变量e常属性
//
// //引用只要不涉及权限的放大即可
// //平移或缩小都是可以的
//
// /*
// * 所以引用即可以做const修饰变量的“别名”
// * 也可以做非const修饰变量的“别名”
// * 有时引用的意义不是为了修改引用实体
// * 而只是为了让引用实体多一个“别名”
// * 可以进行使用
// */
//
// int i 1; //i为整型
//
// double j i;
// //将i隐式转化为浮点型再赋给j
// /*
// * 注类型转化时i不是直接赋给j的
// * 而是会先产生一个临时变量i会先给到临时变量
// * 临时变量再给到j
// */
//
// const double rj i;
// /*
// * 注类型转化时i不是直接赋给j的
// * 而是会先产生一个临时变量i会先给到临时变量
// * 临时变量再给到j临时变量是具有常属性的
// * 所以这里rj引用的是这个具有常属性的临时变量
// * 所以引用时需要给rj也赋予常属性实现“权限的平移”
// */
//}产生临时变量
//int main()
//{
// int i 1; //i为整型
//
// int x 0;
// size_t y 1;
// //注size_t相当于unsigned int
//
// if (x y)
// /*
// * 不同类型的对象在进行在进行运算符运算时
// * 会进行整型提升这里有符号的x会被提升为无符号x,
// * 但这不是直接就将x的类型固定变成了无符号
// * 而是会产生一个临时变量接收无符号的x来代替进行运算符运算
// * 所以这里在比较时本质就是临时变量x和y进行比较x和y都是无符号的
// *
// * 而进行引用操作时是没有产生临时变量的所以不用担心会引用错
// */
// {
//
// }
//
// int* ptr (int*)i; //直接类型转化也是有产生临时变量的
//
// int ii 10000; //整型变量
// char ch ii; //将较大的整型变量赋给字符变量
// /*
// * 类型转换会发生在类型提升、类型截断中
// *
// * 导致类型截断
// * 同理这里整型的ii也不是直接就截断赋给字符类型的ch的
// * 中间也会产生一个临时变量临时变量先接收ii被截断后的值
// * 然后临时变量再赋给ch
// *
// * 只要会发生类型转换就会产生临时变量而临时变量有具有常属性
// * 传值传参、传值返回都会生成拷贝
// */
//}//int main()
//{
// int a 10;
//
// //引用b并没有开空间语法上
// int b a;
//
// //指针ptr是有开空间的语法上
// int* ptr a;
//
// /*
// * 在底层引用和指针是一样的
// *
// * 引用底层
// * 汇编指令 -- lea取地址将变量a的地址放到eax中
// * 汇编指令 -- mov移动将eax放到引用变量n中
// *
// * 指针底层
// * 汇编指令 -- lea取地址将变量a的地址放到eax中
// * 汇编指令 -- mov移动将eax放到指针变量ptr中
// *
// * 所以引用和指针在语法层面上不同但在底层引用和指针是相同的
// * 看起来好像引用没开空间指针有开空间
// * 实际上引用底层和指针一样是开了单独空间的
// * 所以在底层是没有引用这个概念的“引用”也是指针
// * 结论引用底层是用指针实现的
// *
// * 在日常使用中我们还是要以语法层面为主
// * 认为引用是没有单独开空间的来进行引用操作
// * 日常使用不要和指针混淆
// */
//
// return 0;
//}//定义一个ADD宏替换函数
#define ADD(x, y) ((x) (y))
//注宏不加分号
//返回((x) (y))内外层都要加括号//使用内联函数替代宏函数
inline int Add(int x, int y)
{int c x y;return c;
}
/*
* 内联函数克服了宏函数的缺点
* 不用在意返回时括号的设置还可以调试
* 同时还有宏函数的优点不用建立栈帧
* 直接在调用函数的位置就会“展开”内联函数提高效率
* (release发行版本下才会展开)
*debug测试版本下不在意效率所以内联函数还是会建立栈帧
*也可以设置VS编译器的属性设置内联函数即使debug版本也进行展开
*这里演示的就是调整属性后内联函数
*//*
* 内联函数有这么多好处但也不能把所有的函数都写成内联函数
* 内联函数只适合频繁调用的小函数不超过10行代码10行左右
* 举一个反例
* 假设有一个100行的“大内联函数”且对其有10000个调用那么
* 展开合计变成多少行100 * 10000行
* 如果不是内联函数不用展开的话100 10000行
* 将近相差100倍那么编译后分别生成的可执行程序的大小就相差很大了
*
* 所以为了C中为了防止出现“大内联函数”的展开C中也进行了限制
* 内联说明只是向编译器发出的一个请求编译器可以选择忽略这个请求
*///int main()
//{
// int ret1 ADD(2, 3) * 5;
//
// int a 1;
// int b 2;
// int ret2 ADD(a | b, a b);
// // ( (a|b) (ab) )
//
// /*
// * 宏函数的缺点
// * 1、使用时容易出错语法细节多设置括号
// * 2、宏在预处理阶段就替换了不能调试
// * 3、没有类型安全的检查
// */
//
// /*
// * 宏函数的优点
// * 1、不用建立栈帧提高效率
// */
//
// /*
// * 所以C中有
// * enum const inline - 替代宏
// * enum const - 替代宏常量
// * inline - 替代宏函数
// */
//
// int ret3 Add(1, 2);
//
// return 0;
//}//#include Stack.h //包含栈头文件
//
//int main()
//{
// f(10);
// /*
// * 这里调用f()内联函数需要调用该内联函数的地址
// * 因为在编译时只有该内联函数的声明头文件中
// * 头文件中有该内联函数的声明那么编译就能暂时通过
// * 但是到了链接步骤要找该内联函数的地址就找不到了
// * 因为内联函数直接在对应位置就展开了展开就不需要地址了
// * 所以“内联函数不会生成地址”生成的地址不会进入符号表
// * 因此内联函数的声明和实现分离会报链接错误
// */
//
// func();
//
// return 0;
//}auto推断变量的类型
//int main()
//{
// int a 0;
// int b a;
//
// //对变量a的类型进行推导
// auto c a;
// //auto可以自动推导出变量a的类型
//
// //对地址指针的类型进行推导隐式
// auto d a;
// //也可以推导出指针类型隐式
//
// //对地址指针的类型进行推导显示
// auto* e a;
// //和 auto d a; 是一样的显示
//
// //对引用变量的类型进行推导
// auto f a; //f为a的“别名”
// f;
//
//
// //typeid可以打印一个对象的类型
// cout typeid(c).name() endl; //打印c的类型
// cout typeid(d).name() endl; //打印d的类型
// cout typeid(e).name() endl; //打印e的类型
//
// cout typeid(f).name() endl; //打印f的类型
// /*
// * 引用变量的类型为int而不是int,
// * 因为在语法层面f为a的“别名”
// * a的类型为intf作为其“别名”类型也应该是int
// */
//
// /*
// * 虽然auto可以推导变量类型但实际没有什么使用价值
// */
//}auto不能做函数参数
//void func(auto e)
//{
//
//}
//
auto不能做函数返回值
//auto func()
//{
//
//}
//
auto省略类型的定义
//#includevector
//#includestring
//
//int main()
//{
// //有一些类型很长
// vectorstring v;
// vectorstring::iterator it v.begin(); //原生类型写法
// //类型很长vectorstring::iterator
//
// //使用auto自动推导该类型
// auto it v.begin();
// //省略了原生类型很长的写法使用更方便
//
// //注需要初始化才能对相应类型进行推导
// auto x;
//
// //func(5);
//
// return 0;
//}范围for循环
//int main()
//{
// int array[] { 1,2,3,4,5 };
//
// //C语言通过for循环配合数组下标进行对数组的遍历
// for (int i 0; i sizeof(array) / sizeof(array[0]); i)
// {
// array[i] * 2;
// }
//
// cout 当前数组中元素 endl;
//
// //C语言通过指针遍历数组
// for (int* p array; p array sizeof(array) / sizeof(array[0]); p)
// {
// cout *p ;
// }
//
// cout endl; //换行
//
// cout 使用范围for循环打印数组元素 endl;
//
// //C范围for循环
// for (auto e : array)
// /*
// * auto e : array
// * 依次取数组中的值赋值给变量e类型可改
// * 注e只是数组元素的拷贝改变e不会改变数组中的元素
// * 所以这样写范围for循环只能对数组进行打印
// * 而无法修改数组中元素的值
// *
// * 变量e的类型定义为auto可以自动推导array数组中元素的类型
// * 这样数组的类型改变范围for循环遍历时的类型也不用修改
// * 自动判断结束自动往后遍历
// */
// {
// cout e ;
// }
//
// cout endl; //换行
//
// cout 使用范围for循环修改数组元素 endl;
//
// //C范围for循环
// for (auto e : array)
// /*
// * auto e : array
// * 而如果将变量e的类型修改为 auto 的话
// * 就可以实现对数组元素的修改了
// * 因为此时将数组元素赋给e后e就是当前元素的别名了
// * 两者共用同一块内存空间
// * 所以就可以通过修改e来修改对应数组元素了
// */
// {
// //通过e变量修改数组当前元素
// e;
//
// cout e ;
// }
//
// cout endl; //换行
//
// return 0;
//}数组做函数参数的情况下
函数中无法使用范围for循环
//void TestFor(int array[])
//{
// for (auto e : array)
// {
// cout e endl;
// }
//
// /*
// * 这里TestFor函数看似接收了整个数组
// * 但数组传参实际实际是传数组首元素地址
// * 是一个指针所以函数中不能使用范围for循环对其进行操作
// */
//}//指针空值nullptr(C11)void f(int) //只写函数参数的类型没写变量
{cout f(int) endl;//进行类型匹配如果实参是int则打印“f(int)”
}void f(int*) //只写函数参数的类型没写变量
{cout f(int*) endl;//进行类型匹配如果实参是int*则打印“f(int*)”
}/*
* 实参传给形参可以只写形参的类型而不写变量
* 这样无法通过形参改变实参但可以完成参数的匹配
*/int main()
{f(0);f(NULL);/** C语言中NULL实际是一个宏* 将NULL定义为了0* 而不是我们常认为的空指针认为是个指针*/int* ptr1 NULL;f(ptr1);/** C中定义了一个nullptr,* 实际类型为((void*)0)是指针类型*/int* ptr2 nullptr; //这样可以更直观表达空指针f(ptr2);return 0;
}