C++基础入门——语法详解篇(下)

这篇具有很好参考价值的文章主要介绍了C++基础入门——语法详解篇(下)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

文章目录

一、缺省参数

1、1 缺省参数的概念

1、2 缺省参数的分类

1、2、1 全部缺省

1、2、2 半缺省参数

二、引用

2、1 引用的概念

2、2 引用特征

2、3 引用的使用场景

2、3、1 引用做参数

 2、3、2 常引用

2、3、3 引用做返回值

 2、4 引用总结

三、内联函数

3、1 内联函数的引出

3、2 内联函数详解

3、2、1 内联函数的概念

3、2、2 内联函数的特性

四、auto关键字(C++11)

4、1 auto简介

4、2 auto的使用细节

4、2、1 auto与指针和引用结合起来使用

4、2、2 在同一行定义多个变量

4、3 auto不能推导的场景


🙋‍♂️ 作者:@Ggggggtm 🙋‍♂️

👀 专栏:C++ 👀

💥 标题:C++基础语法 💥

 ❣️ 寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景 ❣️

C++基础入门——语法详解篇(下)

  本篇文章解C++基础入门——语法篇(上)来讲述C++基础语法。学玩本篇文章就可以写出简单的C++程序了。

一、缺省参数

1、1 缺省参数的概念

  缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。具体我们可看如下例子:
void Func(int a = 0)
{
 cout<<a<<endl;
}
int main()
{
 Func(); // 没有传参时,使用参数的默认值
 Func(10); // 传参时,使用指定的实参
 
 return 0;
}

  下面为运行结果。我们看到第一个Func函数并没有传参,就默认输出了缺省参数0。 C++基础入门——语法详解篇(下)

1、2 缺省参数的分类

1、2、1 全部缺省

  全部缺省就是我们把形参全部给出缺省值。具体例子如下:

void Func(int a = 10, int b = 20, int c = 30)
{
 cout<<"a = "<<a<<endl;
 cout<<"b = "<<b<<endl;
 cout<<"c = "<<c<<endl;
}

   这种情况我们在调用函数时,可传参,也可选择不传参。但是当我们传的参数只有一个时,默认是从左往右赋值的。也就是先给到a,再给到b,最后给到c。那要是我们想只给a,c赋值且不给b赋值呢?这种情况是不被允许的。我们只能从左往右依次赋值。

1、2、2 半缺省参数

  我们先看具体实例:

void Func(int a, int b = 10, int c = 20)
{
 cout<<"a = "<<a<<endl;
 cout<<"b = "<<b<<endl;
 cout<<"c = "<<c<<endl;
}

   半缺省参数是由几点要注意的:

  1. 半缺省参数必须从右往左依次来给出缺省值,不能间隔着给;
  2. 如果函数的声明和定义分开,缺省值不能在函数声明和定义中同时出现;
  3. 如果函数的声明和定义分开,缺省值在函数的声明时给出即可;
  4. 缺省值必须是常量或者全局变量;
  5. C语言不支持(编译器不支持) 。

二、引用

2、1 引用的概念

  引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

  引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。

  这里给大家举一个例子。我想每个人都有一个小名吧。比如,班级上有一个 高同学 ,我们通常喊他为 小高 。那高同学 和 小高是一个人吗?答案肯定是一个人。我们在这里可以把高同学想象成变量,小高 是高同学的引用,小高犯错等同于高同学犯错。

  我们这里再看一下引用的用法。类型& 引用变量名(对象名) = 引用实体。

void Test()
{
 int a = 10;
 int& ra = a;//<====定义引用类型
 
 printf("%p\n", &a);
 printf("%p\n", &ra);
}

   我们这里看到他们地址是相同的,也就是指向的同一块数据。C++基础入门——语法详解篇(下)

  注意:引用类型必须和引用实体同种类型的。

2、2 引用特征

  在使用引用时,我们需注意以下几点:

  1. 引用在定义时必须初始化;
  2. 一个变量可以有多个引用;
  3. 引用一旦引用一个实体,再不能引用其他实体。
  我们结合下面实例一起理解一下。
void TestRef()
{
 int a = 10;
 int b = 20;
 // int& ra; // 该条语句编译时会出错
 int& ra = a;
 int& rra = a;
 rra = b;  //把b的值赋给rra,不是b的别名
 printf("%p %p %p\n", &a, &ra, &rra); 
}

2、3 引用的使用场景

2、3、1 引用做参数

  引用做参数,虽然大部分情况指针都可完成,但是引用做参数还是比较方便且较为容易理解的,我们看一下实例:  

void Swap(int& left, int& right)
{
 int temp = left;
 left = right;
 right = temp;
}

  上述对两个值交换,我们不再使用指针,直接可以使用引用进行交换。引用做参数还有另一个优势,就是在数据量较大,且数据本身也较大的情况下,引用作为参数的小路就会有所提高。为什么呢?

  以值作为参数,在传参期间,函数不会直接传递实参,而是传递实参的一份临时的拷贝,因此用值作为参数,效率是非常低下的,尤其是当参数非常大时,效率就更低。
  我们可以统一下面代码进行比较就可看出。
#include <time.h>
struct A{ int a[10000]; };
void TestFunc1(A a){}
void TestFunc2(A& a){}
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;
}

  运行结果如下:

C++基础入门——语法详解篇(下)

 2、3、2 常引用

  我们先看如下代码:

const int a = 10;
int& ra = a;

  大家感觉上述的代码有问题吗?答案是有问题的。a 是一个常数,只有读的权限,不能对其修改。而 ra 是对 a 的引用,这里相当于对 a 来说权限放大了。如果正确的话,ra 可以修改 a 的值,但事实上是不可以的。我们应在 ra 前加上const。

   我们再来看一段代码:

double d = 12.34;
 int& rd = d;

  上面的代码正确吗?似乎因并没有错。但好像也错了,也为类型不同。首先上述的代码是有错的。但根本原因并不是类型不同。根本原因在于中间生成了一个临时变量,而临时变量具有常性C++基础入门——语法详解篇(下)

   如下代码就正确了:

 double d = 12.34;
 const int& rd = d;

2、3、3 引用做返回值

   引用做返回值时,有会有一些坑。我们可以看如下例子:

int& Count()
{
 int n = 0;
 n++;
 // ...
 return n;
}

int main()
{
 int ret = Count();
 printf("%d ", ret ); 
 return 0;
}

  表面上似乎并没有什么错误,但实际上已经相当于访问野指针了。Count函数中n出了该函数就销毁了,变量 n 的那段地址空间的使用权限被收回了。Count函数的栈帧也会销毁。有的编译器在栈桢销毁时会清楚数据,有的并不会。在这里可能侥幸打印出正确数据,但确实是错误的。

   函数的传值返回和传引用返回有什么区别呢?我们先来看一下传值返回:

int test()
{
 static int a=0;
 return a;
}

int main()
{
 int& b=test();
 return 0;
}

  我们知道,当a为局部变量时(也就是上述代码中的 a 不加 static ),传值返回时,会把 a 拷贝给一个临时变量,再把临时变量的值赋给 test() 函数。注意:此时的临时变量在main函数中。那要是上述情况呢?同样与局部变量 a 的情况一样,把 a 拷贝给一个临时变量,再把临时变量的值赋给 test() 函数。编译器并不会在这里判断 a 出了作用域是否销毁,传值返回统一把值拷贝给一个临时变量,再把临时变量的值赋给函数

  我们上面了解到了临时变量具有常性,所以上述的代码是有误的。我们应该在引用 b 前加上 const 。

  引用做返回值同样也会有效率问题。以返回值类型为值,在返回期间,函数不会直接者将变量本身直接返回,而是返回变量的一份临时的拷贝,因此用值返回值类型,效率是非常低下的,尤其是返回值类型非常大时,效率就更低。

  通过下面的对比也可看出:

#include <time.h>
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;
}

  运行结果如下:C++基础入门——语法详解篇(下)

 2、4 引用总结

  在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
  我们通过如下代码对比可知:
int main()
{
 int a = 10;
 
 int& ra = a;
 ra = 20;
 
 int* pa = &a;
 *pa = 20;
 return 0
}

  我们看其汇编代码:

C++基础入门——语法详解篇(下)

  其次我们对比一下指针和引用的区别:

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址;
  2. 引用在定义时必须初始化,指针没有要求;
  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体;
  4. 没有NULL引用,但有NULL指针;
  5. 在sizeof中含义不同引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占四个字节);
  6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小;
  7. 有多级指针,但是没有多级引用;
  8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理;
  9. 引用比指针使用起来相对更安全。

三、内联函数

3、1 内联函数的引出

  我们知道函数的调用是有所消耗的,需要开辟栈帧。为了提高效率呢,我们有时候可以选择用宏函数来替代函数调用。例如下面代码就可以用宏函数替代:

//int add(int x, int y)
//{
//	return (x + y) * 10;
//}

#define ADD(x,y) (((x)+(y))*10)
int main()
{
	//int ret = add(1, 2);

	//宏函数
	int ret = ADD(1, 2);

	cout << ret << endl;
	return 0;
}

  注意,在使用宏函数时,一定不要吝啬括号。如果少一些必要的括号的话,会出现预想不到的错误。因为宏是在编译时进行完全的替换。

  但是宏能替换所有的函数吗?答案是不可能的。稍微复杂的函数,用宏来替换就十分复杂。我们再来看一下宏的优缺点,先看优点:

  • 增强代码的复用性;
  • 提高性能;
  我们再来看一下宏的缺点:
  • 不方便调试宏。(因为预编译阶段进行了替换);
  • 导致代码可读性差,可维护性差,容易误用;
  • 没有类型安全的检查 。
  宏的替换并不是很好,且在大多情况下用宏来替换的复杂度很高、可读性差。那还有什么方法呢?C++中引出了内联函数,我们接着往下看。

3、2 内联函数详解

3、2、1 内联函数的概念

  以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。

3、2、2 内联函数的特性

  内联函数有以下特性:

  1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
  2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现),不是递归、频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。下图为《C++prime》第五版关于inline的建议:C++基础入门——语法详解篇(下)
  3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地
    址了,链接就会找不到。
    // F.h
    #include <iostream>
    using namespace std;
    inline void f(int i);
    // F.cpp
    #include "F.h"
    void f(int i)
    {
    	cout << i << endl;
    }
    // main.cpp
    #include "F.h"
    int main()
    {
    	f(10);
    	return 0;
    }
    // 链接错误:main.obj : error LNK2019: 无法解析的外部符号 "void __cdecl f(int)" (?
    //f@@YAXH@Z),该符号在函数 _main 中被引用

四、auto关键字(C++11)

4、1 auto简介

  在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有人去使用它,大家可思考下为什么?
  C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
  我们看auto的使用方法,如下:
#include<iostream>

using namespace std;

int TestAuto()
{
	return 10;
}

int main()
{
	int a = 10;
	auto b = a;
	auto c = 'a';
	auto d = TestAuto();

	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;

	//auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化
	return 0;
}

   运行结果如下:

C++基础入门——语法详解篇(下)

  从这个用例中,我们可大致了解auto的用法及其作用,但是自动推出类型的意义大吗?上述并不能很好的体现出来,我们看下面的一个例子。 

#include <string>
#include <map>
int main()
{
	std::map<std::string, std::string> m{ { "apple", "苹果" }, { "orange", "橙子" },
	{"pear","梨"} };
	std::map<std::string, std::string>::iterator it = m.begin();
	while (it != m.end())
	{
		//....
	}
	return 0;
}

   通过上面的代码,我们可以看出来随着程序越来越复杂,程序中用到的类型也越来越复杂。含义不明确又很容易导致出错。我们第一反应是可以通过typedef给类型取别名,比如:

#include <string>
#include <map>
typedef std::map<std::string, std::string> Map;
int main()
{
	Map m{ { "apple", "苹果" },{ "orange", "橙子" }, {"pear","梨"} };
	Map::iterator it = m.begin();
	while (it != m.end())
	{
		//....
	}
	return 0;
}

   但是auto从不能解决所有的情况。在编程时,常常需要把表达式的值赋值给变量,这就要求在声明变量的时候清楚地知道表达式的类型。然而有时候要做到这点并非那么容易,因此C++11就引出了auto。

  注意:使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。

4、2 auto的使用细节

4、2、1 auto与指针和引用结合起来使用

  用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。具体我们可看下面代码: 

int main()
{
	int x = 10;
	auto a = &x;
	auto* b = &x;
	auto& c = x;  //引用
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	*a = 20;
	*b = 30;
	c = 40;
	return 0;
}

   运行结果如下:

C++基础入门——语法详解篇(下)

4、2、2 在同一行定义多个变量

  当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。我们可结合下面代码一起理解一下:
void TestAuto()
{
    auto a = 1, b = 2; 
    auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}

4、3 auto不能推导的场景

  以下几点都是auto不能推到的场景:文章来源地址https://www.toymoban.com/news/detail-423211.html

  • auto不能作为函数的参数;
    // 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
    void TestAuto(auto a)
    {}
  • auto不能直接用来声明数组;
    void TestAuto()
    {
       int a[] = {1,2,3};
       auto b[] = {4,5,6};  //错误
    }
  • 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法;
  • auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等进行配合使用。

到了这里,关于C++基础入门——语法详解篇(下)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】C++入门基础:引用详解

    本篇继续分享关于C++入门的相关知识,有关命名空间、缺省参数和函数重载的部分欢迎阅读我的上一篇文章【C++】C++入门基础详解(1)_王笃笃的博客-CSDN博客 继续我们的学习 在C语言中我们接触过指针,很多人都或多或少为他感到头痛过,很多C语言的使用者包括创始人都觉

    2024年02月10日
    浏览(38)
  • 【C++】C++入门基础详解(1)

    本篇内容要分享的是C++的基础内容,C++的诞生简单的说就是为了填补C语言中的语法坑,同时对比C语言来说增添很多便捷的语法规则,使用起来比C语言便捷不少,但是学习难度也大大增强,不过难度是成线性增长,可以一步一步的深入学习。 目录 1.命名空间 1.1命名空间指定

    2024年02月12日
    浏览(37)
  • C++入门基础(万字详解!!!)

      C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机, 20世纪80年代, 计算机界提出了OOP(object oriented programming:面向对象)思想,支持面向对象的程序设计语言应运而

    2024年02月13日
    浏览(34)
  • 【C++】入门基础知识详解(二)

    目录 一、内联函数 1、概念 2、特性 3、内联函数与宏的优缺点 二、auto(C++11)   1、auto 简介   2、auto的使用细则     2.1 auto与指针和引用结合起来使用     2.2 在同一行定义多个变量   3、auto不能推导的场景     3.1 auto 不能作为函数的参数     3.2 auto 不能直接用来声

    2024年02月11日
    浏览(37)
  • 【C++】入门基础知识详解(一)

    目录 一、C++ 二、命名空间   1、命名空间的定义   2、命名空间的使用 三、C++输入输出 四、缺省参数   1、缺省参数的概念   2、缺省参数的分类     2.1 全缺省参数     2.2 半缺省参数 我们在学习C++之前,我相信大家大多数都对C语言多多少少都有所了解,C语言的关键

    2024年02月12日
    浏览(51)
  • 【C++初阶】第一站:C++入门基础(上) -- 良心详解

    前言:         从这篇文章开始,将进入C++阶段的学习,此篇文章是c++的第一站的上半篇,讲述C++初阶的知识 目录 什么是C++ C++的发展史 C++(C++98) 命名空间 命名空间定义 命名空间使用 1.加命名空间名称及作用域限定符 2.使用using将命名空间中某个成员引入 3.使用using na

    2024年02月04日
    浏览(40)
  • 【C++开篇 -- 入门语法篇】

    前言: 首先,C++兼容C,C++在C语言范畴上增添了一些优化,它支持多种编程范式,包括过程式编程、面向对象编程和泛型编程。C++是C语言的扩展,添加了许多新特性,如类、模板、异常处理等。接下来,随着这篇文章推开C++知识的大门吧。 / 知识点汇总 / 1.1、什么是C++? C++是

    2024年02月19日
    浏览(32)
  • 【Python 零基础入门】基础语法

    当我们学习一门新语言, 首先要熟悉它的语法规则. 这就如同学习一门外语, 我们需要知道句子的结构, 词汇的使用和语法的规则. 与 Java 中的 “{}” 不同, Python 使用缩进. 缩进在 Python 中非常重要, 定义了代码的结构和层次. 通常用 4 个空格作为标准的缩进 (TAP 键). 在我们编写代

    2024年02月04日
    浏览(41)
  • C++语法——详解虚继承

    目录 一.什么是虚继承 二.虚继承原理 三.虚继承使用注意事项 所谓虚继承(virtual)就是子类中只有一份间接父类的数据。该技术用于解决多继承中的父类为非虚基类时出现的数据冗余问题,即菱形继承问题。 小编用一张图来表述一下: 如果是下图这种非虚继承 ,那么 D类中

    2024年01月16日
    浏览(38)
  • C++基础语法——继承

    目录 1.继承是什么? 2.如何定义与书写继承? 3.继承关系与访问限定符及访问关系的变化 4.基类与派生类的赋值转换 5.基类与派生类成员的作用域 6.继承中的默认成员函数 7.继承中的友元 8.继承中的静态成员 9.菱形继承与虚拟菱形继承 ①菱形继承是什么? ②菱形继承的缺点

    2024年02月12日
    浏览(27)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包