C++ 学习中遇到问题总结

关键字

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函数增加代码的可读性。

c++ 在函数后加const的意义:

关键字new


new double();
new double[n];

第一种:new一个double类型的变量a, 内存大小就是double类型这个大,并且用括号里的值去初始化;

第二种:new一个double类型的变量b,大小时n个double类型,利用默认的构造函数去初始化

运算符重载

看一个函数声明。

83b44b90cd9ba9047bcf1b49a813ff48.png

红框的部分可以直接看作函数名;括号里是参数;最后一个表明函数只读,增强代码可读性。

通过成员函数重载+号

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博客

c++智能指针转化:static_pointer_cast、dynamic_pointer_cast、const_pointer_cast、reinterpret_pointer_cast_Glücklichste的博客-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;
}
c3561d7f111004771974c75eb010fb7c.png
7ad3078692c8938a822fdfad6cd10fc9.png

抽象类不能被实例化

使用指针或者引用。

在自己的实际工程中遇到一个问题,我只拿到了父类的智能指针:

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()仍然是函数。

以及在函数声明里面给了默认参数,那么函数定义就不能再给了。

右值引用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值