收获
1.#ifndef防止头文件重复包含
为了避免同一个头文件被包含(include)多次,C/C++中有两种宏实现方式:一种是#ifndef方式;另一种是#pragma once方式。
#ifndef 标识符A
//每一个头文件都要有自己独特的标识
//(有一定的规则,但是主要是自己定义的,看心情哈哈哈)
//这句话是在判断该头文件是否定义过,
//如果定义过,则执行#endif后面的内容
//没有定义过,则执行#define里面的内容执行定义
#define 标识符A
……//这里面写头文件的东西啦
#endif
......//假如头文件被定义过了,然后要干啥可以写在这里,一般没啥可干的
C语言中,常常一些头文件被多次包含(#include" "),这样就可能出现嵌套包含现象,比如a.h文件被包含进b.h文件,而a.h文件与b.h文件又被包含进c.h文件中,如此a.h文件在c.h文件中出现了两次,这样不仅影响预处理的效率,有时还会引发错误,所以我们想办法在a.h中做些标记,使得a.h被其他文件多次包含时只处理第一次。
这里的“标识符A”是自己定义的,但每一个文件里的该“标识符A”必须是唯一的。
而诸多老师为了方便辨认,以及宏名常用大写表示,
所以常将 “标识符A” 写成 “_头文件名大写_H” 。
实际上这里的“标识符A”的名称与头文件名称没有什么必然联系。
(1)#ifndef
#ifndef的方式受C/C++语言标准支持。它不仅可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件(或者代码片段)不会被不小心同时包含。
当然,缺点就是如果不同头文件中的宏名不小心“撞车”,可能就会导致你看到头文件明明存在,但编译器却硬说找不到声明的状况——这种情况有时非常让人郁闷。
由于编译器每次都需要打开头文件才能判定是否有重复定义,因此在编译大型项目时,ifndef会使得编译时间相对较长,因此一些编译器逐渐开始支持#pragma once的方式。
(2)#pragma once
#pragma once 一般由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。
你无法对一个头文件中的一段代码作pragma once声明,而只能针对文件。
== 其好处是,你不必再担心宏名冲突了==,当然也就不会出现宏名冲突引发的奇怪问题。大型项目的编译速度也因此提高了一些。
对应的缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。当然,相比宏名冲突引发的“找不到声明”的问题,这种重复包含很容易被发现并修正。
另外,这种方式不支持跨平台!
2. 关键字explicit防止隐式转化
C++提供了关键字explicit,可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生,声明为explicit的构造函数不能在隐式转换中使用。
C++中, 一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数), 承担了两个角色。
1 是个构造;2 是个默认且隐含的类型转换操作符。
所以, 有时候在我们写下如 AAA = XXX, 这样的代码, 且恰好XXX的类型正好是AAA单参数构造器的参数类型, 这时候编译器就自动调用这个构造器, 创建一个AAA的对象。
这样看起来好象很酷, 很方便。 但在某些情况下, 却违背了程序员的本意。 这时候就要在这个构造器前面加上explicit修饰, 指定这个构造器只能被明确的调用/使用, 不能作为类型转换操作符被隐含的使用。
解析:explicit构造函数是用来防止隐式转换的。请看下面的代码:
#include <iostream>
using namespace std;
class Test1
{
public :
Test1(int num):n(num){}
private:
int n;
};
class Test2
{
public :
explicit Test2(int num):n(num){}
private:
int n;
};
int main()
{
Test1 t1 = 12;
Test2 t2(13);
Test2 t3 = 14;
return 0;
}
编译时,会指出 t3那一行error:无法从“int”转换为“Test2”。而t1却编译通过。注释掉t3那行,调试时,t1已被赋值成功。
注意:当类的声明和定义分别在两个文件中时,explicit只能写在在声明中,不能写在定义中。
3.override告诉编译器该函数用于重载父类的某个虚函数
例程
成员函数为虚函数
我们的意图是在子类Derived1与Derived2分别重载父类Base::print(void),
在我们下面的代码中有没有发现什么问题呢?
class Base {
public:
virtual void print(void){...}
};
class Derived1 : public Base {
public:
void print(void){...}
};
class Derived2 : public Base {
public:
void Print(void){...}
};
额…,原来因为不小心,在Derived2中将print误输入为Print,(注意其中的字母p的大小写),关键问题是编译器完全可以正确的编译上面的代码,这是一个很难发现的错误。
问题来了: 如何简单直接的向编译器表明意图,我就是要重载Base::print(void)呢?如果我写错了字母,你(指代编译器)要直接告诉我错误,或者一个警告也行?
答: override关键字可以做这件事情。
代码修改如下:
class Base {
public:
virtual void print(void){...}
};
class Derived1 : public Base {
public:
void print(void) override {...}
};
class Derived2 : public Base {
public:
void Print(void) override {...}
};
当编译器看到上面代码的时候就开始很不爽了,编译器开始抱怨了,你通过 “override” 告诉我 "Derived2::Print(void)是要重载父类的某个函数,可是我根本没有在父类中找到 "Print(void)"函数,编译器直接丢出个错误,然后甩膀子不干了。
你知道的,编译器都是很娇贵的,当他甩脸色的时候,你也只能乖乖的仔细看他给你的错误,哦,原来是不小心写错字母了,一切就这么完美的解决了。
总结
override用于直接明了的告诉编译器该函数用于重载父类的某个虚函数
另外:虚函数的子函数前面加不加vitual都可以
因为:
c++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此,在子类从新声明该虚函数时,可以加,也可以不加,但习惯上每一层声明函数时都加virtual,使程序更加清晰。
4. 大括号初始化
空大括号赋值会创建一个值初始化的临时量并赋值给赋值对象 以保证空间存在
vector vi{1, 2, 3, 4, 5};
接下来是赋值的情况:
vector vi;
vi = {6, 7, 8, 9, 10};
这种形式,对于有限多个数值的赋值是非常有用的。
补充
和初始化一样,使用大括号包围的值列表也有同样的优势。一个是防止窄化,有时可以简单的理解为防止精度降低,例如下面的代码是无法编译通过的:
double pai = 3.1415926;
int pi;
pi = {pai}; //编译错误。
另外,如果大括号里的初始化列表为空,编译器会创建一个值初始化的临时量并赋值给赋值对象。
4.取整函数
C++常用的取整函数有三个ceil,floor,round
头文件
#include<cmath>
ceil的英文释义:装天花板,装船内格子板;函数功能是向上取整
floor的英文释义:楼层; 地面,地板;函数功能是向下取整
round的英文释义:大约;函数功能是四舍五入
示例:
int _tmain(int argc, _TCHAR* argv[])
{
float num = 8.6f;
std::cout << num << " 向上取整为: "<< ceil(num) <<std::endl;
std::cout << num << " 向下取整为: "<< floor(num) << std::endl;
std::cout << num << " 四舍五入为: "<< round(num) << std::endl;
getchar();
return 0;
}
5.随机数引擎
rand()
基本:使用随机数时,经常见到的是C标准库提供的函数rand(),这个函数会生成一个0到RAND_MAX之间的一个整形数;
分布:为了得到一个给定范围内的随机数,通常会对生成的随机数取余:rand()%n,rand()%(n-m)+m;
种子:通过srand()设置随机数种子,种子不变的情况下,每次程序运行,调用rand(),都会生成相同的随机数序列;
浮点:使用double(rand())/RAND_MAX可以生成0-1范围内的随机浮点数,但精度会有问题;
一般情况下,srand()种子可以使用time(0)进行设置,time()取系统的秒,所以如果srand()和rand()的调用间隔小于1s,则会生成相同的随机数,如:
#include <stdlib.h> /* srand, rand */
#include <time.h> /* time */
for (size_t i = 0; i < 10; i++)
{
srand((unsigned int)time(NULL));
cout << rand() << endl;
}
C++11 : default_random_engine
基本:C++11提供了新的随机数生成器,随机数引擎default_random_engine,使用时包含头文件#include;
范围:默认情况下,default_random_engine的生成范围是一个unsigned,可以通过方法min()和max()获取生成范围;
种子:与rand()类似,default_random_engine也需要通过随机数种子改变生成的序列,设置方法可以通过调用方法seed();
分布和浮点:随机数引擎可以通过分布对象设置生成范围,
uniform_int_distribution<unsigned>
或
uniform_real_distribution<double>;
相对rand(),可以使用uniform_real_distribution<>生成随机浮点数,并且不用担心精度问题,随机数引擎的使用方法如下:
#include<random>
default_random_engine e;//定义随机数引擎
uniform_int_distribution<unsigned> id(1, 10);//整型分布
uniform_real_distribution<double> dd(0, 1.0);//浮点型分布
e.seed(10);//设置随机数种子
for (size_t i = 0; i < 10; i++)
{
cout << id(e) << " ; " << dd(e) << endl;
}
设置random_device rd
这玩意是c++11引入的新的头文件
#include<random>
里面可以实现真随机数的玩意儿
可以给引擎设置这个种子,达到随机数的牛逼plus
std::random_device rd; // Non-determinstic seed source
std::default_random_engine rng3 {rd()}; // Create random number generator
种子值是通过== random_device 类型的函数对象 rd 获得的。每一个 rd() 调用都会返回不同的值,==而且如果我们实现的 random_devic 是非确定性的,程序每次执行连续调用 rd() 都会产生不同的序列。
设置种子
方法
default_random_engine e;//定义随机数引擎
e.seed(10);
//创建后再设置种子
default_random_engine n{10};
default_random_engine e1(32767);
//创建引擎时设置种子
随机种子
default_random_engine e(time(0));
//设置当前时间为引擎种子
random_device rd; // Non-determinstic seed source
default_random_engine rng3 {rd()}; // Create random number generator
//用random_device 产生的真随机数来赋值
6.程序运行计时
#include<windows.h>
double TIME=0;
LARGE_INTEGER nFreq;
LARGE_INTEGER nBeginTime;
LARGE_INTEGER nEndTime;
QueryPerformanceFrequency(&nFreq);
QueryPerformanceCounter(&nBeginTime);//开始计时
......//中间运行的程序
QueryPerformanceCounter(&nEndTime);//停止计时
TIME = (double)(nEndTime.QuadPart - nBeginTime.QuadPart) / (double)nFreq.QuadPart;//计算程序执行时间单位为s
cout << TIME * 1000 <<"ms"<< endl;//乘以1000单位为毫秒