关于文案的网站,html5基础知识,com域名注册流程,网站建设规划pptC#xff1a;基于C的语法优化 命名空间命名空间域域作用限定符展开命名空间域 输入输出缺省参数全缺省参数半缺省参数 函数重载参数类型不同参数个数不同参数类型的顺序不同 引用基本语法按引用传递返回引用引用与指针的区别 内联函数autoauto与指针和引用结合 范围for循环nul… C基于C的语法优化 命名空间命名空间域域作用限定符展开命名空间域 输入输出缺省参数全缺省参数半缺省参数 函数重载参数类型不同参数个数不同参数类型的顺序不同 引用基本语法按引用传递返回引用引用与指针的区别 内联函数autoauto与指针和引用结合 范围for循环nullptr C语言是基于C语言优化而出的函数由于C语言是一门比较古早的语言。随着学者们对计算机的理解不断加深越来越好用的语法与概念提出C语言就遗留了许多历史问题。C之父为了优化C语言的问题于是不断为其添加新语法新概念逐渐衍生出了一门新的语言C。
命名空间
先看到一段C语言的代码
#include stdio.h
#include stdlib.hint rand 1;int main()
{printf(%d, rand);return 0;
}这段代码看似没有问题但是运行后编译器会报出“rand重定义”的问题。 这是因为我们引入了头文件stdlib.h而其内部有rand函数用户的变量名与头文件冲突了。 这该这么解决 在C语言中好像没有什么很好的办法让不同头文件中的同名变量共存只能让其中一者改变自己的变量。
C设计者认为这个特性不利于项目合作当我们对多个员工编写的C语言代码进行合并时就有可能出现此时只能让其中一者修改代码。 C为此设计了一种新的域命名空间域。
命名空间域
在不同的域中是可以存在同名变量的而C语言只存在局部域与全局域两种域。C的命名空间域则是一种可以根据用户需要自己定义的域。
语法
namespace (名称)
{//代码
}命名空间域通过关键字namespace指定其大括号内部算作单独的一块域不受外界域的影响比如这样
namespace A
{int a 1;
}namespace B
{int a 2;
}int a 3;int main()
{printf(%d, a);return 0;
}在以上代码片中我们有三个变量aa 1处于命名空间域A中a 1处于命名空间域B中而a 1处于全局中。
我们此时输出printf(%d, a)会输出谁 答案是3。
其变量的基本查找规则如下 现在当前作用域查找如果当前作用域查找不到就向上级作用域查找直到查找到全局作用域如果此时还没有编译器报错 所以a在访问时会访问到全局的a。
命名空间域有以下特性 当两个命名空间域重名两个域内部的代码会合并作用域可以嵌套变量结构体函数等等都可以写入这个域中 那么我们要如何访问到我们自己指定的命名空间域中的变量呢 这就要通过域作用限定符了 域作用限定符
::是C中的域作用限定符将其放在变量前可以改变此变量的查找规则使之直接到指定域中查找。
比如以下代码
namespace A
{int a 1;
}int a 3;int main()
{printf(%d, A::a);return 0;
}其中A::a就是直接在命名空间域中查找a变量。 所以代码输出1。
此外域作用限定符左侧没有值时默认到全局变量查找。 这一点很重要因为在基本的查找规则中是先查找局部作用域再查找全局作用域的。而当::左侧没有值时会直接跳过局部变量在全局中查找。 比如以下代码
int a 3;int main()
{int a 4;printf(%d, ::a);return 0;
}上述代码的输出结果是3。 虽然在局部中有一个a 4但是::a会直接跳过局部直接去全局查找所以最后输出了3.
访问嵌套的命名空间域 想要访问嵌套的命名空间域只需要依据从外层-内层的顺序利用::将每个名称分隔开就可以访问了如下
namespace A
{namespace B{namespace C{int a 2;}}
}int main()
{printf(%d, A::B::C::a);return 0;
}以上代码中我们嵌套了三层命名空间域在访问a时从外层到内层按照A::B::C::a访问。
所以我们可以按照如下方式解决不同文件变量可能存在冲突的问题每个.cpp文件最外层用一个命名空间域包含起来后续引入文件时每个人编写的文件独自享有一个域就不会发生冲突问题了。 比如这样
user1.cpp
namespace user1
{int a 0;int b 1;int Add(int x, int y){return x y;}
}user2.cpp
namespace user2
{int a 1;int b 0;float Add(float x, float y){return x y;}
}每一份.cpp文件都用一个命名空间域包在最外层需要使用谁的代码时就到哪一个空间域中查找。
那如果这样的话在main函数中想要访问其它文件内的内容是不是很冗余几乎大部分变量都要加上::前缀这就太麻烦了。于是又产生了展开命名空间域这一功能。 展开命名空间域
所谓展开命名空间域就是对某个空间域进行展开将其内部的变量放到全局中。也就是说一个空间域的内容经过展开后就会变成全局变量而变量查找规则中最后一层就是在全局中查找所以可以不使用::就访问到想要的变量。 语法
using namespace (名称);示例
namespace user1
{int a 0;int b 1;
}using namespace user1;int main()
{printf(%d, a);printf(%d, b);return 0;
}在以上示例中我们使用using namespace user1;将user1展开了此时user1内部的变量就被释放到全局了后续就无需对ab使用域限定操作符也可以直接使用了。
但是有时候我们并不是需要一个命名空间域中的所有内容如果将整个空间域有些没必要。 此时我们可以使用部分展开
using (名称)::(变量名)示例
namespace user1
{int a 0;int b 1;int Add(int x, int y){return x y;}
}using user1::Add;
using user1::a;int main()
{printf(%d,a);Add(3, 5);return 0;
}上述代码中我们创建了一个空间域user1其内部有ab两个变量以及Add函数。 随后将using user1::Add;和using user1::a;进行了部分展开。 最后我们就可以直接访问变量a以及调用Add函数了。 输入输出
C的输入输出是基于对象的操作但是此处仅做入门知识讲解所以不深入讲解只讲解基本输入输出语句。
输出语句
cout Hello World endl;在以上语句中cout本质是一个对象如果你无法理解什么是对象那么可以暂时理解它是一个控制台可以看到输出语句。 随后利用了流插入运算符你可以理解为将Hello World这个字符串放到了cout中随后 endl的意思是换行endl相当于C语言中的\n用于换行。所以以上语句也可以写成:
cout Hello World \n;效果是一致的。
输入语句 C的输入语句是通过cin对象其可以获取用户输入的内容。那么我们要如何获得cin提取的内容 利用流提取操作符就可以提取到cin的返回值。比如这样
int a 0;
cin a;以上代码就可以实现用户输入一个值将其赋值给a。
相比于C语言的输入输出需要使用%d%s%f这样的占位符来控制输入类型。C的输入输出操作明显的优势就是自动识别类型。其中cout可以拆分为c out所以用于输出cin可以拆分为c in所以用于输入 缺省参数
全缺省参数
缺省参数是值可以为函数的参数设置初始值如果调用时没有传入参数则此参数以初始值调用函数。
比如以下代码
int Add(int x 5, int y 10)
{return x y;
}int main()
{Add(1, 2);Add(1);Add();return 0;
}上述代码中我们定义了一个函数Add其带有两个参数x和y其中为x设置初始值x 5给y设置初始值y 10。
第一次调用Add(1, 2);为xy都传了参数此时完成的是1 2。 第二次调用Add(1);只为x传入了参数此时y以初始值调用此函数完成的是1 10。 第三次调用Add();没有传入参数此时x和y都以初始值调用此函数完成的是5 10。
这种参数缺省叫做全缺省参数即所有的参数都赋予了初始值哪怕一个参数都不传也可以调用函数。 注意传入参数必须从左往右传入不能有空缺。 比如以下代码
int Add(int x 5, int y 10, int z 20)
{return x y z;
}int main()
{Add(1, ,6);return 0;
}此代码中Add(1, ,6);的意图是让x 5z 20让y取初始值。但是这是不允许的调用函数时必须从左向右连续传入不能间断地缺省参数。
半缺省参数
半缺省参数是指缺省参数时有一些值不赋予初始值必须传入值。
比如这样
int Add(int x, int y 10, int z 20)
{return x y z;
}此时x就是一个不可以被缺省的参数在调用函数时必须为x传入值。 要注意半缺省参数中不赋予初始值的参数必须从左往右连续不可以间断地缺省。 比如以下情况
int Add(int x 5, int y, int z 20)
{return x y z;
}此代码中x是被缺省的那么其右边的y和z也必须被缺省不能跳过y直接缺省z。
最后还有一个注意点不能在声明和定义时同时缺省参数。 什么意思呢 看到一个示例 test.h文件中
void func(int a 10);test.cpp文件中
void func(int a 10)
{cout a * 5 endl;
}以上代码我们将函数声明在了test.h文件中声明在了test.cpp文件中。 这样会造成重定义的错误程序无法运行如果想要将缺省参数声明在.h文件中那么在定义时就不要写出缺省参数了。 以上代码的正确形式如下
test.h文件中
void func(int a 10);test.cpp文件中
void func(int a)
{cout a * 5 endl;
}函数重载
函数重载是指C允许在同一作用域中声明的同名函数但是其必须遵守一项规则保证同名函数的形参列表不同。
形参列表不同就是要求满足以下三者之一 函数的参数个数不同函数的参数类型不同函数的参数类型的顺序不同 接下来我带大家理解这三种情况。
参数类型不同
void Add(int left, int right)
{cout I am int Add endl;
}void Add(double left, double right)
{cout I am double Add endl;
}在以上代码中我们定义了两次Add函数第一次定义时两个参数的类型都是int而第二次定义时两个参数的类型都是double此时两个Add函数就构成了重载。 在调用Add函数时会根据传入参数的类型来决定调用哪一个函数。 比如以下代码
int main()
{int a 1;int b 2;Add(1, 2);double c 3.0;double d 4.0;Add(c, d);return 0;
}第一次调用Add(1, 2);传入了两个int变量此时与函数void Add(int left, int right)类型匹配调用此函数输出I am int Add 。
第二次调用Add(c, d);传入了两个double变量此时与函数void Add(double left, double right)类型匹配调用此函数输出I am double Add。 参数个数不同
void f()
{cout f() endl;
}void f(int a)
{cout f(int a) endl;
}void f(int a, int b)
{cout f(int a, int b) endl;
}以上代码中我们定义了三个f函数三者的区别就是函数的参数个数不同那么我们传入不同数量的参数也就会调用不同的函数了。
int main()
{f();f(1);f(1, 2);return 0;
}第一次调用f();没有传入参数与void f()参数数目匹配调用此函数。 第二次调用f(1);传入一个参数与void f(int a)参数数目匹配调用此函数。 第三次调用f(1, 2);传入两个参数与void f(int a, int b)参数数目匹配调用此函数。 参数类型的顺序不同
void f(int a, char b)
{cout f(int a,char b) endl;
}void f(char b, int a)
{cout f(char b, int a) endl;
}以上代码中我们定义了两个函数f第一个函数的参数列表为int, char第二个参数的参数列表为char, int此时两个参数类型的顺不同构成函数重载。 示例
int main()
{int a 0;char b 0;f(a, b);f(b, a);return 0;
}第一次调用传入了f(a, b);与参数列表int, char匹配调用函数输出f(int a,char b)。 第二次调用传入了f(b, a);与参数列表char, int匹配调用函数输出f(char b, int a)。 引用
基本语法
C的引用是一种特殊的变量类型用于给已经存在的变量起一个别名。通过引用我们可以通过一个已存在的变量名来访问和操作另一个变量的值。
引用可以被看作是一个已存在变量的别名引用和被引用的变量始终指向同一块内存空间对引用的操作实际上就是对被引用变量的操作。
引用的语法如下
type 别名 变量名;其中type是被引用变量的类型。
下面是一个使用引用的简单示例
int main() {int num 10;int ref num; // 创建一个引用ref指向numcout num的值为 num endl; // 输出num的值为10cout ref的值为 ref endl; // 输出ref的值为10// 通过引用修改num的值cout num的新值为 num endl; // 输出num的新值为20cout ref的新值为 ref endl; // 输出ref的新值为20return 0;
}在上面的示例中我们创建了一个整数变量num并通过引用ref给它起了一个别名。后续通过引用ref来修改num的值实际上就是对num的直接操作。
其中 int num 10;int ref num; ref 20;相当于 int num 10;int* ref num;*ref 20;需要注意的是引用不同于指针它不能指向空值或者没有初始化的变量。因此在定义引用时必须保证所引用的变量已经存在并且在定义引用时必须进行初始化。
也就是说下面的语句是非法的
int a;这语句中a是一个引用但是它没有初始化此时编译器会报错。 但是在指针中
int* a;是合法的。 引用其实不单单只是代替指针这么简单其还可以作为返回值参数等。
按引用传递
C中的按引用传递是一种参数传递方式它允许函数通过引用来操作调用者提供的实参。
按引用传递是将实参的引用传递给形参。
按引用传递的语法是在函数的参数前加上符号。例如以下的函数原型中使用了按引用传递
void Function(int x);按引用传递有以下几个作用 通过引用传递参数可以避免对大型对象的复制。当传递一个大型对象时按值传递会进行一次复制操作而按引用传递只需要传递对象的引用而不需进行复制从而提高了程序的效率。 通过引用传递参数可以实现函数对实参的修改。在函数内部通过引用可以直接操作实参对实参的修改会在函数外部产生影响。而按值传递只能修改函数内部的形参副本对实参没有影响。
比如我们想实现一个交换函数 利用指针来实现
void Swap(int* a, int* b)
{int tmp *a;*a *b;*b tmp;
}int main()
{int x 1;int y 3;Swap(x, y);return 0;
}此函数中不仅需要多次对参数解引用而且每次调用都需要对变量取地址用起来还是有点难受的。
此时我们可以利用按引用传递实现 void Swap(int a, int b)
{int tmp a;a b;b tmp;
}int main()
{int x 1;int y 3;Swap(x, y);return 0;
}相比于刚才的代码这串代码就畅快多了一方面是在函数内部使用参数时不用额外解引用在传参时也不需要取地址了。
总之按引用传递是一种高效且灵活的参数传递方式可以减少内存的复制操作实现对实参的修改。在C中通过引用传递可以提高程序的效率和可读性。 返回引用
在C中返回引用是指从函数中返回一个引用类型的值。返回引用的主要目的是允许函数返回一个对于某个变量的引用从而允许在函数外部对该变量进行修改。
返回引用的主要用途有以下几个
允许函数直接修改函数外部的变量。允许在函数调用中连续进行操作类似于链式操作。优化性能避免创建临时对象。
下面通过案例来分别说明这几个功能
允许函数直接修改函数外部的变量
int increment(int num) {num;return num;
}int main() {int num 5;increment(num) 10;cout num endl; // 输出为 10return 0;
}在上面的例子中increment函数返回了对num的引用。在main函数中我们可以直接对increment(num)进行赋值操作相当于对num进行了修改。
允许在函数调用中连续进行操作
int add(int num, int value) {num value;return num;
}int main() {int num 5;add(add(num, 3), 2);cout num endl; // 输出为 10return 0;
}在上面的例子中add函数返回了对num的引用。我们可以连续调用add函数每次都对num进行修改。
优化性能避免创建临时对象
string concatenate(string str1, const string str2) {str1 str2;return str1;
}int main() {string str1 Hello;string str2 World;concatenate(str1, str2) !;cout str1 endl; // 输出为 Hello World!return 0;
}在上面的例子中concatenate函数返回了对str1的引用。通过返回引用我们可以直接对str1进行修改避免了创建临时对象。在调用concatenate函数的时候我们可以将返回的引用与另一个字符串连接操作进行连续调用。
需要注意的是返回引用时被返回的变量应该仍然存在否则返回的引用就会变成悬空引用可能导致不可预期的行为。此外如果返回引用指向了一个局部变量函数返回后该变量将被销毁返回的引用将变得无效。因此返回引用时需要确保引用的有效性。 引用与指针的区别 引用概念上定义一个变量的别名指针存储一个变量地址。引用在定义时必须初始化指针没有要求引用在初始化时引用一个实体后就不能再引用其他实体而指针可以在任何时候指向任何一个同类型实体没有NULL引用但有NULL指针在sizeof中含义不同: 引用结果为引用类型的大小但指针始终是地址空间所占字节个数引用自加即用的实体增加1指针自加即指针向后偏移一个类型的大小有多级指针但是没有多级引用访问实体方式不同指针需要显式解引用引用编译器自己处理引用比指针使用起来相对更安全 内联函数
在讲解内联函数前我们前看看C语言中的宏的缺点。 C语言宏的缺点有以下几个 没有类型检查: 宏是在预处理阶段进行替换没有类型检查的机制。因此使用宏时要特别小心否则可能会出现类型不匹配的错误。可读性差: 宏通常会展开为较长的代码可能会使代码变得难以阅读和理解。特别是在宏内部使用复杂的表达式或多行代码时会使代码的可读性大大降低。可能引起副作用: 宏通常会直接对参数进行替换可能会导致意外的副作用。例如一个宏可能会多次计算参数的值如果参数是一个函数调用或者是一个带有副作用的表达式那么可能会引发错误。可能导致重复的代码: 使用宏可能导致代码中出现大量的重复代码。当多个地方使用相同的宏时如果需要修改宏的实现方式就需要修改所有使用该宏的地方增加了代码维护的复杂性。调试困难: 宏在展开后的代码中看不到宏本身的定义因此在调试时很难跟踪和查找问题。由于宏在编译阶段被替换调试器无法直接定位到宏的定义位置这给调试带来了一定的困难。 综上所述虽然宏在C语言中具有一定的灵活性和便利性但也存在一些缺点。在使用宏时应当谨慎特别是在处理复杂的表达式或有副作用的代码时应考虑使用其他更安全和可读性更高的替代方法。
C认为宏是一个不太好的特性于是在C中推荐使用enum枚举和const替换掉宏常量。用内联函数inline替换掉宏函数。
于是内联函数被设计了出来。
被inline修饰的函数叫做内联函数在编译时C编译器会在调用内联函数的地方将内联函数展开不额外创建栈帧来执行函数提高程序的效率。没错这也是宏函数最重要的一点不会创建栈帧。内联函数延续的宏函数的优点但是又做了许多优化。
比如以下函数就是一个内联函数
inline int Add(int x, int y)
{int z x y;return z;
}在函数的前方加一个inline关键字这样调用函数时函数就会直接在目标位置展开。
在相比于宏函数内联函数会对参数类型进行确定防止错误类型的传入。
如果宏函数非常长那么对其展开时会导致代码重复性非常高这已经违背了函数设计的初衷代码复用。 所以内联函数有另外一个特性当函数体内部代码长度超过一定值时其会转化为普通函数不会直接展开而是创建栈帧防止代码冗余。 auto
在C中auto关键字可以用来自动推断变量的类型它在编译时会根据初始化表达式的类型来确定变量的类型。
使用auto的主要好处是可以简化代码并提高可读性。它可以减少手动指定变量类型的工作并且可以防止类型错误。相比于显式指定变量类型使用auto可以让代码更加灵活和易于维护。
自动推断基本类型变量的类型
auto age 25; // 推断age为int类型
auto salary 5000.50; // 推断salary为double类型自动推断容器中迭代器的类型
std::vectorint numbers {1, 2, 3, 4, 5};
for (auto it numbers.begin(); it ! numbers.end(); it) {std::cout *it ;
}如果你看不懂这一段也没关系这一段主要是讲解有的时候获得变量的类型会需要很长的代码。使用auto可以缩短变量类型的长度。
auto与指针和引用结合
auto也可以自动推断指针的类型比如这样
int x 10;
auto y x;此时y的类型自动判别为int*。 那么我们可不可以为auto加上*来识别指针
看到一段代码
int x 10;auto* a1 x;
auto* a2 x;
auto a3 x;在auto* a1 x;中x的类型是int那么auto本应将其值判别为int但是由于auto*被*限制了此时auto必须得到一个指针所以编译器会报错而auto* a2 x;得到的就是指针此时代码不会报错可以正常识别为int*。
在本质上auto* a2 x;和auto a3 x;的结果是没有区别的只是auto*要求得到的必须是一个指针类型而auto不限制其类型。 同理的auto会要求必须是一个引用类型否则会报错。
auto也有许多限制要注意以下问题 auto不能作为函数的参数 auto不能用于声明数组
比如以下代码
int arr1[] {1, 3, 5, 7, 9};
auto arr2[] {1, 3, 5, 7, 9};此时第二条代码就会报错因为其用auto类型定义了一个数组。
在同一行定义多个变量时如果将auto作为其类型必须一整行都是同一个类型的变量。
比如以下代码 int x 1, y 2;auto a 3, b 4;auto c 5, d 6.0;以上代码中auto a 3, b 4;是合法的因为一行内都是int类型。 但是auto c 5, d 6.0;是非法的因为同一行内有不同类型会报错。 范围for循环
范围for循环是C11引入的一种新的循环结构它可以方便地遍历数组或者其他具有迭代器的对象。
范围for循环的语法如下
for (auto element : collection) {// 执行语句
}其中element 是一个临时变量用来存储集合中的每个元素的副本collection 是一个可迭代的对象可以是数组或者其他具有迭代器的对象。 其中auto也可以换为intfloat等类型只是结合auto会更好用。
下面是一个简单的例子
int main() {int numbers[] {1, 2, 3, 4, 5};for (auto element : numbers) {cout element ;}return 0;
}输出结果为1 2 3 4 5
在上面的例子中我们定义了一个整型数组 numbers范围for循环遍历了整个数组每次迭代将数组中的一个元素赋值给临时变量 element然后我们将该元素输出到控制台。
如果你希望修改这个数组内部的值可以在auto后加上将其变为一个引用。
就像这样 for (auto element : numbers) {element * 2;}就可以完成元素的乘以2的操作。 nullptr
在C11标准中引入了nullptr关键字来表示空指针。C推荐使用nullptr而不是使用传统的NULL宏定义。
看到一段代码 这串代码是C对NULL的定义其本质是一个宏如果在C语言环境允许那么NULL就是((void*)0)也就是将整型0强制转化为了void*的0地址。
但是当运行环境是CNULL就被定义为0这导致空指针可能被识别为整型。所以C引入了nullptr替代NULL。
NULL在传统的C中只是一个宏定义为0会被隐式转换为整型这可能导致一些类型安全性问题。nullptr不会被隐式转换为其他类型只能赋值给指针类型从而避免了潜在的类型错误。
其次是代码清晰度nullptr相比于NULL更加直观明了能够更好地表示空指针的含义即null ptrnull表示空ptr表示指针。这样可以提高代码的可读性。
所以在C中定义一个空指针最好用nullptr。