引用
传值、传引用效率的比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直 接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效 率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
用下面代码来比较传值和传引用的效率
代码语言:javascript复制#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;
}
注意:0不是表示调用时间为零,而是表示调用时间极短,计算机将其近似的看做是零
从上面的运行结果可以看出,传引用效率明显比传值的效率高的多
内联函数
概念:以inline修饰的函数叫做内联函数,编译时C 编译器会在调用内联函数的地方展开,没有函数调 用建立栈帧的开销,内联函数提升程序运行的效率。
内联函数的使用场景:在一个项目中一个函数经常被调用而且代码量很小,这时我们就可以将其用inline修饰成内联函数,但是内联函数在计算机中,到底用了inline之后是不是内联函数,这个取决于编译器,这个权限时编译器决定的,而不是我们使用了inline,这个函数就是内联函数。
假如这个权限给了使用者的话,当我们在使用内联函数时,假设调用者滥用,将会使一个原本只需要几kb的文件最后编译出来会比原来大的多,因为inline修饰的函数是不会建立栈帧的,如果函数内部的代码量特别大,在调用时用了内联函数,在编译过程中会使代码特别大
内联函数的特性
- inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会 用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运 行效率。
- inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建 议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不 是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。
- 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)
auto带来的便利
代码语言:javascript复制从字面意思来考察auto就是自动的意思,不难看出auto就是我们可以先给定后面变量的类型,然后auto可以帮我们自动识别这个变量是什么类型,从我们现在学习编程的角度来看,可能意义不大,因为int还有char等等这些类型都是比较简单的,但当我们越往后学习,还有很多类型,代码量特别大,所以我们引入了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;
}
代码语言:javascript复制
std::map<std::string, std::string>::iterator
是一个类型,但是该类型太长了,特别容 易写错。这句代码也等价于下面的代码:
#include <string>
#include <map>
int main()
{
std::map<std::string, std::string> m{ { "apple", "苹果" }, { "orange","橙子" }, {"pear","梨"} };
auto it = m.begin();
while (it != m.end())
{
//....
}
return 0;
}
上面代码看着就简洁的多
auto的使用规则
注意:如果auto后面带了一个
*
则后面的变量只能是指针类型的变量,如果前面的auto什么也不加,则后面的变量可以是任何类型的变量。
auto不能作为参数进行传参
代码语言:javascript复制// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
auto也不能用来直接声明数组
代码语言:javascript复制void TestAuto()
{
int a[] = {1,2,3};
auto b[] = {4,5,6};
}
基于范围的for循环
代码语言:javascript复制以往的遍历数组:
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
for (int i = 0;i < sizeof(arr) / sizeof(arr[0]);i )
{
cout << arr[i] << " ";
}
return 0;
}
代码语言:javascript复制学习了范围for之后,可以这用遍历数组:
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
for (auto e : arr)
{
cout << e << " ";
}
return 0;
}
注意:范围for的遍历数组对应的数组的范围必须是确定的
代码语言:javascript复制对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供 begin和end的方法,begin和end就是for循环迭代的范围。 注意:以下代码就有问题,因为for的范围不确定
void TestFor(int array[])
{
for(auto& e : array)
cout<< e <<endl;
}
注意:如果用返回for进行遍历数组时要对数组中的元素进行修改,必须用引用,引用在for循环当中的e只是auto的一份临时拷贝,所以在范围for的遍历数组当中必须进行引用
指针空值(C 11)
在C 中,以前的C语言中的NULL,在底层的定义中是0,所以为了区别,就重新定义了一个
nullptr
,在编程中可以不用包含任何头文件可以直接使用nullptr
类和对象
在C语言中,注重的编程的过程,在C 中注重的面向的对象,所以在C 中引入了类和对象的概念
类的引入
代码语言:javascript复制C语言结构体中只能定义变量,在C 中,结构体内不仅可以定义变量,也可以定义函数。比如: 之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C 方式实现, 会发现struct中也可以定义函数。
typedef int DataType;
struct Stack
{
void Init(size_t capacity)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(const DataType& data)
{
// 扩容
_array[_size] = data;
_size;
}
DataType Top()
{
return _array[_size - 1];
}
void Destroy()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
DataType* _array;
size_t _capacity;
size_t _size;
};
int main()
{
Stack s;
s.Init(10);
s.Push(1);
s.Push(2);
s.Push(3);
cout << s.Top() << endl;
s.Destroy();
return 0;
}
上面是结构体的定义方式,在C 中更偏向于用class
类的定义
代码语言:javascript复制class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。
在class中的成员变量可以随意访问
类的访问限定符及封装
访问限定符
- public修饰的成员在类外可以直接被访问
- protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
- 如果后面没有访问限定符,作用域就到 } 即类结束。
- class的默认访问权限为private,struct为public(因为struct要兼容C)
C 的class
和C语言中struct的区别
C 中的class默认是private,C语言中的struct默认是Public,这是C 为了兼容C语言,如果struct和class一样默认都是private,那么C 就兼容不了C语言了