C++程序设计语言学习笔记:选择适当的操作

本文介绍了C++中的运算符,包括逻辑运算符、位逻辑运算符、条件表达式和递增/递减运算符。讨论了动态内存管理,如new和delete的使用,以及可能出现的内存问题。还涉及了lambda表达式和匿名函数对象的使用,以及如何在不同场景下使用{}

1、逻辑运算符与(&&)、或(||)、非(!)接受算术类型以及指针类型的运算对象,将其转换为bool类型,最后返回一个bool类型的结果。只有当逻辑上确实需要时,&&和||才会对其第二个实参求值。

2、位逻辑运算符与(&)、或(|)、非(~)、右移(>>)和左移(<<)作用于整形对象。位逻辑运算符常用于实现一个小集合的概念。二元运算符&表示求交操作,|表示求并操作,^表示异或操作,~表示求补操作。

3、在条件表达式c?e1:e2中使用了一对可选的表达式e1和e2,这对表达式的类型必须相同,或者它们都能隐式地转换成同一种类型T。

4、++运算符表示递增的含义,--表示递减含义。

5、运算符new负责创建对象,运算符delete则负责销毁它们。new分配的对象“位于自由存储之上”(在堆上或在动态内存中)。

6、自由存储的问题主要包括:

  • 对象泄露:使用new,但是忘记用delete释放分配的对象。
  • 提前释放:在尚有其他指针指向对象并且后续仍会继续使用该对象的情况下过早delete。
  • 重复释放:同一对象被释放两次,两次调用对象的析构函数。

7、自由存储运算符new、delete、new[]和delete[]的实现位于<new>头文件中。

8、通常我们把提供额外的实参给operator new()的new(buf) X语法称为放置语法

9、我们能用{}列表初始化命名变量,此外,很多地方{}列表还能作为表达式出现。

struct S{int a, b;};

void f(S);        //f接受一个S
void g(S);        

void h()
{
    f({1,2});     //调用f(S{1,2})
    g(S{1,2});    //调用g(S)
}

10、{}列表的实现模型由三部分组成:

  • 如果{}列表被用作构造函数的实参,则其实现过程与使用()列表类似。
  • 如果{}列表被用于初始化一个聚合体的元素,列表的每个元素分别初始化聚合体中的一个元素。
  • 如果{}列表被用于创建一个initializer_list对象,则列表的每个元素分别初始化initializer_list的底层数组的一个元素。

11、如果你能用下面语句初始化一个变量x:

        T x {v};

        那么你也能用T{v}或者new T{v}的形式创建一个对象并将其当成一条表达式。

struct S{int a, b;};

void f()
{
    S v{7, 8};            //直接初始化一个变量
    v = S{7, 8};          //用限定列表进行赋值
    S* p = new S{7,8};    //使用限定列表在自由存储上构建对象
}

12、当我们明确知道所用类型时,可以使用未限定列表。它只能被用作一条表达式,并且仅限于:函数实参、返回值、赋值运算符的右侧对象、下标。

13、当我们在不使用=的前提下,把未限定的{}列表用作命名对象的初始化器时,该列表直接执行初始化。其他情况下,它执行拷贝初始化。

14、标准库类型initializer_list<T>用于处理长度可变的{}列表。

int high_value(initializer_list<int> val)
{
    int high = numeric_traits<int> lowest();
    if(val.size() == 0) return high;

    for(auto x:val)
    {
        if(x>high) high = x;
    }
    return high;
}

int v1 = high_value({1,2,3,4,5,6,7});
int v2 = high_value({-1,2,v1,4,-9,20,v1});

15、lambda表达式,有时也成为了lambda函数,或者直接简称为lambda。它是定义和使用匿名函数对象的一种简便的方式。当我们想把操作当成实参传给算法时,显得尤为重要。

16、一条lambda表达式包含一下组成要件:

  • 一个可能为空的捕获列表。捕获列表位于[]内。
  • 一个可选的参数列表。参数列表位于()内。
  • 一个可选的mutable修饰符,指明该lambda表达式可能会修改它自身的状态。
  • 一个可选的noexcept修饰符。
  • 一个可选的->形式的返回类型声明。
  • 一个表达式体,指明要执行的代码。表达式体位于{}内。

17、lambda的主要用途是封装一部分代码以便于将其用作参数。lambda允许我们“内联地”这么做,而无需命名一个函数(或者函数对象)然后在别处使用它。有些lambda无须访问它的局部环境,这样的lambda使用空引入符[]定义。

void algo(vector<int>& v)
{
    sort(v.begin(), v.end());    //排列值
    sort(v.begin(), v.end(), [](int x, int y){return abs(x)<abs(y);});    //排列绝对值 
}

18、lambda引入符的形式有很多种:

  • []:空捕获列表。意味着数据需要从实参或者非局部变量中获得。
  • [&]:通过引用隐式捕获。所有局部名字都能使用,所有局部变量都通过引用访问。
  • [=]:通过值隐式捕获。所有局部名字都能使用,所有名字都指向局部变量的副本。
  • [捕获列表]:显示捕获;捕获列表是通过值或者引用的方式捕获的局部变量的名字列表。
  • [&,捕获列表]:对于名字没有出现在捕获列表中的局部变量,通过引用隐式捕获。
  • [=,捕获列表]:对于名字没有出现在捕获列表中的局部变量,通过值隐式捕获。
void f(vector<int>& v)
{
    bool sensitive = true;
    // ...
    sort(v.begin(), v.end(), [sensitive](int x, int y){ return sensitive ? x<y : abs(x)<abs(y);});
}

19、如果我们发现lambda的生命周期可能比它的调用者更长,就必须确保所有局部信息(如果有的话)都被拷贝到闭包对象中,并且这些值应该通过return机制或者适当的实参返回。

20、名字空间变量(包括全局变量)永远是可访问的(确保在作用域内),所以我们无须“捕获”它们。

21、当lambda被用在成员函数中时,我们如何访问类对象的成员呢?我们的做法是把this添加到捕获列表中,这样类的成员就位于可捕获的名字集合中了。

22、当我们希望修改函数对象(闭包)的状态时,可以把lambda声明成mutable的。

void algo(vector<int>& v)
{
    int count = v.size();
    std::generate(v.begin(), v.end(), [count]()mutable{return --count;});
}

23、如果一条lambda表达式不接受任何参数,则其参数列表可被忽略。如:[]{}。

24、lambda表达式的返回类型能由lambda表达式本身推断得到,而函数不行。

25、C++提供了多种显式类型转换的操作,这些操作在便利程度和安全性上都有所不同:

  • 构造,使用{}符号提供对新值类型安全的构造。
  • 命名的转换,提供不同等级的类型转换:
    • const_cast,对某些声明为const的对象获得写入的权利。
    • static_cast,反转一个定义良好的隐式类型转换。
    • reinterpret_cast,改变位模式的含义。
    • dynamic_cast,动态地检查类层次关系。
  • C风格的转换,提供命名的类型转换或其组合。
  • 函数化符号,提供C风格转换的另一种形式。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值