关键字
inline :
1、引入 inline 关键字的原因
在 c/c++ 中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了 inline 修饰符,表示为内联函数。
栈空间就是指放置程序的局部数据(也就是函数内数据)的内存空间。
在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足而导致程序出错的问题,如,函数的死循环递归调用的最终结果就是导致栈内存空间枯竭。
2、inline使用限制
inline 的使用是有所限制的,inline 只适合函数体内代码简单的函数使用,不能包含复杂的结构控制语句例如 while、switch,并且不能内联函数本身不能是直接递归函数(即,自己内部还调用自己的函数)。
3、inline仅是一个对编译器的建议
inline 函数仅仅是一个对编译器的建议,所以最后能否真正内联,看编译器的意思,它如果认为函数不复杂,能在调用点展开,就会真正内联,并不是说声明了内联就会内联,声明内联只是一个建议而已。
4、建议 inline 函数的定义放在头文件中
其次,因为内联函数要在调用点展开,所以编译器必须随处可见内联函数的定义,要不然就成了非内联函数的调用了。所以,这要求每个调用了内联函数的文件都出现了该内联函数的定义。
因此,将内联函数的定义放在头文件里实现是合适的,省却你为每个文件实现一次的麻烦。
声明跟定义要一致:如果在每个文件里都实现一次该内联函数的话,那么,最好保证每个定义都是一样的,否则,将会引起未定义的行为。如果不是每个文件里的定义都一样,那么,编译器展开的是哪一个,那要看具体的编译器而定。所以,最好将内联函数定义放在头文件中。
5、类中的成员函数与inline
定义在类中的成员函数默认都是内联的,如果在类定义时就在类内给出函数定义,那当然最好。如果在类中未给出成员函数定义,而又想内联该函数的话,那在类外要加上 inline,否则就认为不是内联的。
class A
{
public:void Foo(int x, int y) { } // 自动地成为内联函数
}
将成员函数的定义体放在类声明之中虽然能带来书写上的方便,但不是一种良好的编程风格,上例应该改成:
// 头文件
class A
{
public:
void Foo(int x, int y);
}
// 定义文件
inline void A::Foo(int x, int y){}
头文件里是函数声明,没有花括号。而定义的文件里,有花括号。
类成员函数加const:
char getData() const{
return this->letter;
}
有些类的成员函数会改变成员变量的值;而有些不改变,说明该成员函数类似“只读”的。如上面这个例子,加上const函数增加代码的可读性。
关键字new
new double();
new double[n];
第一种:new一个double类型的变量a, 内存大小就是double类型这个大,并且用括号里的值去初始化;
第二种:new一个double类型的变量b,大小时n个double类型,利用默认的构造函数去初始化
运算符重载
看一个函数声明。
红框的部分可以直接看作函数名;括号里是参数;最后一个表明函数只读,增强代码可读性。
通过成员函数重载+号
C++ static、const 和 static const 类型成员变量声明以及初始化
const 定义的常量在超出其作用域之后其空间会被释放,而 static 定义的静态常量在函数执行后不会释放其存储空间。(在实际工作中遇到过一个问题:给类的一个成员方法前加static,该方法中声明了一个vector容器。 在一个单元测试中,两次调用该方法,会发现第二次调用这个静态方法的时候,vector容器不为空,里面存有上一次调用的相关值)。
类的静态成员函数、静态成员变量不属于某个对象,而是类本身所有的。即使没有具体对象,也能调用类的静态成员函数和成员变量。一般类的静态函数几乎就是一个全局函数,只不过它的作用域限于包含它的文件中。
static 关键字只能用于类定义体内部的声明中,定义时不能标示为 static。
在 C++ 中,const 成员变量也不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数。
const 数据成员 只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其 const 数据成员的值可以不同。所以不能在类的声明中初始化 const 数据成员,因为类的对象没被创建时,编译器不知道 const 数据成员的值是什么。
const 数据成员的初始化只能在类的构造函数的初始化列表中进行。要想建立在整个类中都恒定的常量,应该用类中的枚举常量来实现,或者static cosnt。
class Test{
public:
Test():a(0){}
enum {size1=100,size2=200};
private:
const int a;//只能在构造函数初始化列表中初始化
static int b;//在类的实现文件中定义并初始化
conststatic int c;//与 static const int c;相同。
};
int Test::b=0;//static成员变量不能在构造函数初始化列表中初始化,因为它不属于某个对象。
cosnt intTest::c=0;//注意:给静态成员变量赋值时,不需要加static修饰符,但要加cosnt。
强制类型转换static_cast以及static_pointer_cast
可以使用(int)x,但是为了语法的规范,最好使用static_cast<int>(x)
/*
一般的强制类型转换,也可以用来转换自己构造的数据类型
*/
double a;
int b = static_cast<int>(a);
/* 智能指针的强制类型转换,如将一个父类指针强转成子类的指针
注意头文件 #<memory> 以及加std::
*/
std::shared_ptr<Curve2d> lineOffset2 = line.Offset(1.1)[0]; //创建一个父类Curve2d的智能指针
//std::shared_ptr<Line2d> lineSegOffsetSPtr = static_pointer_cast<Line2d>(lineOffset2); 不加std也会报错
std::shared_ptr<Line2d> lineSegOffsetSPtr = std::static_pointer_cast<Line2d>(lineOffset2);
EXPECT_TRUE(line.Dir().IsEqual(lineSegOffsetSPtr->Dir())); //EXPECT_TRUE是单测中的基础断言; Dir()是子类的成员方法(父类中没有),因此需要强制转换
除此之外还有std::dynamic_cast,std::dynamic_pointer_cast(dynamic这种更安全一些)等,具体可参考:c++ 父类指针如何操作子类的新函数_基类指针怎么调用子类函数_宛城学子的博客-CSDN博客
explicit
当一个构造函数被声明为explicit,就不允许隐式转换
C++ explicit 关键字 - 知乎 (zhihu.com)
const double& 这种写法的原因
/*
Circle2d表示一个圆的类
Origin()是Point2d的成员方法,返回原点
GetRadius()是Circle2d获得圆半径的成员方法
*/
Circle2d circle(Point2d::Origin()); //构造一个对象圆circle
const double& radius = circle.GetRadius();
// 等价于:double temp; const int& radius = temp;
/*
传参的时候
*/
double GetNewRadius(const double& radius);
传入一个引用避免拷贝构造,但是以引用的方式传入的radius可能会被函数更改,加const则不会被改
返回了一个double类型的常量引用,那么radius就不能被调用的人更改。
特别地,用const double& 修饰传入的形参:1. 左值引用传参避免拷贝 2. const避免形参改变了实参
decltype
decltype 是 C++11 新增的一个关键字,它和 auto 的功能一样,都用来在编译时期进行自动类型推导。auto 并不适用于所有的自动类型推导场景,所以 decltype 关键字也被引入到 C++11 中。
左值、右值以及move
简单理解(并不准确):左值是既可以出现在等号左边也可以出现等号右边的值,而右值只能出现在等号右边。
- 左值是可以寻址的,是持久的;
- 右值一般是不可寻址的常量,或在表达式求值过程中创建的无名临时对象,短暂性的。
左值引用和右值引用
int a = 1; //a是左值, 1是右值
int &b = a; //左值引用,即给左值a起了一个别名叫b
int &c = a*2; //错误!!此时a*2是右值,不能寻址
const int& d =a*2; //正确,可以将常引用绑定到右值
int &&c = a*2; //正确,右值引用
int &&d = a; // 错误,a是一个左值
move
根据工作需要,此处只理解特定场景下的使用:vector::push_back使用std::move提高性能
// 例:std::vector和std::string的实际例子
int main() {
std::string str1 = "aacasxs";
std::vector<std::string> vec;
vec.push_back(str1); // 传统方法,copy
vec.push_back(std::move(str1)); // 调用移动语义的push_back方法,避免拷贝,str1会失去原有值,变成空字符串
vec.emplace_back(std::move(str1)); // emplace_back效果相同,str1会失去原有值
vec.emplace_back("axcsddcas"); // 当然可以直接接右值
}
// std::vector方法定义
void push_back (const value_type& val);
void push_back (value_type&& val);
void emplace_back (Args&&... args);
下面是一个更简单的例子:
std::vector<int> b(5);
b[0] = 2;
b[1] = 2;
b[2] = 2;
b[3] = 2;
// 此处用move就不会对b中已有元素重新进行拷贝构造然后再放到a中
std::vector<int> a = std::move(b);
遇到的问题以及解决方式
把引用作为函数返回值
好处在于:这样就可以把函数放在赋值语句左边。
注意:作用域的问题。不能返回一个局部变量的引用。但是静态变量可以。
#include <iostream>
using namespace std;
double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
double& setValues(int i) {
double& ref = vals[i];
return ref; // 返回第 i 个元素的引用,ref 是一个引用变量,ref 引用 vals[i]
}
// 要调用上面定义函数的主函数
int main ()
{
cout << "改变前的值" << endl;
for ( int i = 0; i < 5; i++ )
{
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
setValues(1) = 20.23; // 改变第 2 个元素
setValues(3) = 70.8; // 改变第 4 个元素
cout << "改变后的值" << endl;
for ( int i = 0; i < 5; i++ )
{
cout << "vals[" << i << "] = ";
cout << vals[i] << endl;
}
return 0;
}
抽象类不能被实例化
使用指针或者引用。
在自己的实际工程中遇到一个问题,我只拿到了父类的智能指针:
BoundedCrv2dSPtr bdCrvSPtr = m_contour.Curve(i).BdCurve();
我的目标是,对bdCrvSPtr指向的值,调用它的成员函数Reverse()。
如果直接BoundedCrv2d bdCrv = *(bdCrvSPtr ) 或者 BoundedCrv2d bdCrv = *(m_contour.Curve(i).BdCurve())会报错不能实例化一个抽象类。
应该创建一个子类的指针去调用它的内容。
刚开始想利用auto自动类型推断:
auto bdCrvSPtr = m_contour.Curve(i).BdCurve();
bdCrvSPtr->Reverse();
但这种写法并不好,而且根据自己后面的需求,发现这样写还容易拷贝构造:
bool dir = m_holes[j].Curve(k).SameDir();
if (!dir)
{
auto bdCrvSPtr = m_holes[j].Curve(k).BdCurve();
bdCrvSPtr->Reverse();
region[j + 1].emplace_back(bdCrvSPtr);
}
else
region[j+1].emplace_back(m_holes[j].Curve(k).BdCurve());
因此上面的代码可以直接改为:
bool dir = m_holes[j].Curve(k).SameDir();
if (!dir)
m_holes[j].Curve(k).BdCurve()->Reverse();
region[j + 1].emplace_back(m_holes[j].Curve(k).BdCurve());
即对于无法实例化抽象类的问题,用指针去调用子类的成员方法。
vector容器的初始化
vector容器中经常出错的问题
vector<vector<BoundedCrv2dSPtr>> region;
使用的时候记得初始化,它现在只是声明了一个变量region。
若直接去做region[0].example_back(1)是错误的,因为region没有初始化,并不知道region的大小,所以region[0]是错误的。
初始化:
// 初始化
region.push_back(std::vector<BoundedCrv2dSPtr>());
函数使用默认参数
TriangleMesh Triangulate(const CrvFacetParams& crvOptions = CrvFacetParams::CreateCrvFacetParams(), const Plane& plane = Plane());
此时第二个参数仍为无参构造,记得构造函数名只是和类名相同,此处的Plane()仍然是函数。
以及在函数声明里面给了默认参数,那么函数定义就不能再给了。