首页 > 编程语言 >Effective C++ 第一章:让自己习惯C++

Effective C++ 第一章:让自己习惯C++

时间:2024-02-14 23:12:37浏览次数:29  
标签:const 函数 Effective 对象 C++ 第一章 static local

Effective C++ 第一章:让自己习惯C++

引言

最近在阅读这本《effective C++ 改善程序与设计的55个具体做法》这本书,为了以后忘记的时候回顾,写一些笔记,每次笔记大概记录一个章节的内容。

条款1.视C++为一个语言联邦

C++最早只是C语言的扩充,在C基础上加上了面向对象特性,但是发展了很多年后,变的庞大了很多。
现在的C++是一个多重范式编程语言,支持面向过程,面向对象,函数形式,泛型形式,元编程形式。

至少记住这四项,C,object-orented-C++,template-C++,STL,分别是C语言,面向对象的C++,C++模板,STL库(template程序库)。

条款2.尽量以enum,const,inline替换掉define

宁可以编译器替换掉预处理器

情况1

#define ASPECT_RATIO 1.653

这个记号名称可能会在编译器处理源码前被预处理器移动走了,没有进入记号表内(symbol table),于是你运行这个常量就会获得一个编译错误信息而浪费时间。

解决方法

const double AspectRatio = 1.653

const肯定会被编译器看到,当然会进入记号表内,防止编译问题。

情况2

你想创建一个class专属常量

class cl{
private:
static const int NumTurns=5; //常量的声明式
int arr[NumTurns];
}

c++要求对你所使用的都提供一个定义式,但上面的class专属常量并且static就可以例外。如果你想要获取常量的内存地址,或者有一些编译器坚持需要定义式,你需要提供定义式如下:

const int cl::NumTurns; //NumTurns的定义

如果你不想让你class专属常量内存地址被访问,你可以用enum类型去替代

class cl{
private:
enum {NumTurns=5}; //让NumTurns成为5的一个记号名称
int arr[NumTurns];
}

情况3

define创建的宏函数

template<class T>
T f(T x)
{
return x;
}
#define CALL_WITH_MAX(a,b) f((a)>(b) ? (a) : (b))

上面的宏函数作用是比较两个数大小,并且返回大的数字,但是这个宏函数很容易产生意外。

int a=5,b=0;
std::cout<<a<<" "<<b<<std::endl;   //5  0
CALL_WITH_MAX(++a,b); 
std::cout<<a<<" "<<b<<std::endl;   //7  0   
CALL_WITH_MAX(++a,b+20); 
std::cout<<a<<" "<<b<<std::endl;   //8  0

上面的a在和比较的时候自增了两次,为了避免这种bug,我们换成inline模板函数来代替它

template<class T>
T f(T x)
{
return x;
}
template<typename T>
inline void callWithMax(const T& a,const T& b)
{
	f(a>b ? a : b);
}

int a=5,b=0;
std::cout<<a<<" "<<b<<std::endl;   //5  0
CALL_WITH_MAX(++a,b); 
std::cout<<a<<" "<<b<<std::endl;   //6  0   
CALL_WITH_MAX(++a,b+20); 
std::cout<<a<<" "<<b<<std::endl;   //7  0

对于单纯常量,最好以const对象或者enums代替#defines

对于形似函数的宏,最好改用inline函数代替#defines

条款3.尽可能使用const

const允许你使用语义约束,告诉编译器这个值保持不变。

情景1

char str[] = "hello";
char *p = str; //不是const指针,也不是const数据
const char *p = str; //不是const指针,是const数据
char const *p=str;  //是const指针,不是const数据
const char const *p=str; //是const指针,是const数据

const的规则,const在左边,代表了指针指向的数据是const,const在右边,代表了指针本身是const。

情景2

class cl{
public:
...//省略构造函数析构函数
char& operator[](std::size_t position)const   //代表const成员函数
{
return pText[position];
}
...
private:
  char *pText;
};

上面的const成员函数看似没有修改class内部成员,其实已经发生了泄漏。

const cl class_cl("hello");
char *cp=&class_cl[0];
cp='J';   //这里修改了const的数据内容

所以请记得给上面的const成员函数加上const,并且记住const成员函数承诺任何时候都不修改其对象的逻辑状态。

条款4 确定对象被使用前已经被初始化

读取未初始化的值可能导致不明确的行为,甚至在一些平台上会导致你的程序终止。

弄清楚赋值和初始化的区别:

int x=0; //初始化
x=1; //赋值

在class的构造函数中,初始化和赋值容易搞混淆

class cl{
...
  cl(int &data):num(data){}  //初始化成员变量
  cl(int *p)
  {    
  num=*p;//这里num先被默认初始化一个值,然后被赋值*p
  }
...
private:
int num;
}

使用初始化列表比赋值的效率会更高,只调用了一次构造函数,而不是先调用默认构造函数再赋值。

请以local static对象代替 non-local static对象

在C++中,local static对象non-local static对象的主要区别在于它们的生存期和作用域。

Local static对象是指在函数内部声明并使用static关键字修饰的变量或对象。它们在函数第一次被调用时创建,并一直存在于函数的整个生命周期内,直到函数结束时才被销毁。Local static对象的作用域仅限于其所在的函数,不能在其他函数中访问。

Non-local static对象是指在函数外部声明并使用static关键字修饰的变量或对象。它们在程序启动时创建,并一直存在于程序的整个生命周期内,直到程序结束时才被销毁。Non-local static对象的作用域是全局的,可以在任何函数中访问。

以local static对象代替non-local static对象意味着将non-local static对象改为在函数内部声明并使用static关键字修饰。这样做有以下几个优点:

提高代码的局部性:Local static对象的作用域仅限于其所在的函数,可以提高代码的局部性,使代码更容易理解和维护。

减少内存占用:Non-local static对象在程序的整个生命周期内都存在,即使在不再使用时也无法释放内存。而local static对象在函数结束后就会被销毁,可以减少内存占用。

提高安全性:Non-local static对象可以在任何函数中访问,这可能会导致意外修改或污染。而local static对象只能在函数内部访问,可以提高安全性。

当然,以local static对象代替non-local static对象也有一些缺点:增加了代码的复杂度 ,降低了代码的效率。

// Non-local static object
static int global_count = 0;

void foo() {
  global_count++;
  // ...
}

// Local static object
void bar() {
  static int local_count = 0;
  local_count++;
  // ...
}

在这个例子中,global_count是一个non-local static对象,在任何函数中都可以访问。而local_count是一个local static对象,只能在bar()函数中访问。

在foo()函数中,每次调用都会增加global_count的值。而在bar()函数中,每次调用只会增加local_count的值。

如果foo()函数被频繁调用,那么global_count的值会迅速增长,可能会导致内存占用过高。而bar()函数的local_count值只会在一个函数内累加,不会影响其他函数。

标签:const,函数,Effective,对象,C++,第一章,static,local
From: https://www.cnblogs.com/AndreaDO/p/18000289

相关文章

  • C++多线程 第五章 C++内存模型和原子类型
    第五章C++内存模型和原子类型无论其他语言如何,C++是一门系统编程语言.委员会希望不再需要一个比C++低级的语言.内存模型基础C++程序中所有的数据均是由对象(object)组成的.C++标准定义对象为"存储区域",经管它会为这些对象分配属于它们的类型和生存期.无论什么类型,对象......
  • C++ map自定义比较函数遵守严格弱序
    C++map自定义比较函数遵守严格弱序问题背景及定位背景:这个问题是在将tablesaw(一个Java的数据处理项目)迁移到C++时出现的。问题位置:SplitOn()函数,在数据流水线中的aggregate阶段。问题描述:使用google/benchmark进行了批量化的性能测试,在测试中出现偶发性段错误,几率大约在万分......
  • C++——编译和链接原理笔记
    我们在学习和开发C++程序中,理解编译和链接的原理至关重要。下面将学习一下C++程序是如何从源代码转换为可执行文件的过程,并结合示例代码进行说明。也是为了解开自己在刚学习C++的时候,编译时间长的疑惑。为了不让自己的学习之路这么枯燥,我按照一个正常的开发流程梳理一下......
  • c++定义类的时候,只提供拷贝构造函数而不提供默认(无参)构造函数和有参构造函数会怎样?
    4.2.4构造函数调用规则默认情况下,c++编译器至少给一个类添加三个函数:默认构造函数(无参,函数体为空)默认析构函数(无参,函数体为空)默认拷贝构造函数(对属性进行值拷贝)构造函数调用规则:如果用户定义有参构造函数,编译器不会提供默认(无参)构造,但是会提供默认拷贝构造函数如果用户......
  • 第一章 Python概述
     第一章、Python概述 1.Python是什么 2.Python语言的特点 3.Python语言的缺点 4.Python程序的执行过程 5.安装Python 2.1通过Python官网安装包来安装 2.2使用pip安装第三方库 2.3通过anaconda安装Python 2.4两种Python安装方式比较 6.运行Python......
  • C++——异常处理模块笔记
    异常处理是C++中的重要概念之一,用于处理在程序执行过程中可能发生的错误或异常情况。异常是指在程序执行过程中发生的一些不寻常的事件,例如除零错误、访问无效内存等。C++提供了一套异常处理机制,使得程序可以优雅地处理这些异常,提高程序的可靠性和健壮性。异常是一种程序......
  • 【C++】两两交换链表中的节点
    #include<iostream>#include<stack>usingnamespacestd;structListNode{intval;ListNode*next;ListNode(intx):val(x),next(nullptr){}};ListNode*swapPairs1(ListNode*head){ListNode*dummyHead=newListNode(0);dummyHead......
  • 【C++】给定两个增序的链表,试将其合并成一个增序的链表。
    给定两个增序的链表,试将其合并成一个增序的链表。#include<iostream>#include<stack>usingnamespacestd;structListNode{intval;ListNode*next;ListNode(intx):val(x),next(nullptr){}};voidprintList(ListNode*head){while(head){std:......
  • 【C++】假设链表中每一个节点的值都在 0 - 9 之间,那么链表整体就可以代表一个整数。
    题目:假设链表中每一个节点的值都在0-9之间,那么链表整体就可以代表一个整数。给定两个这种链表,请生成代表两个整数相加值的结果链表。数据范围:0≤n,m≤1000000,链表任意值0≤val≤9要求:空间复杂度O(n),时间复杂度O(n)例如:链表1为9->3->7,链表2为6->3,最后生成新的结果链表......
  • 程序是怎样跑起来的第一章读后感
    对于大部分人来说,CPU可能只是一个抽象的概念,存在于每一个电子设备中,但却很少被真正了解。我的初衷是为了更深入地理解这个计算机的核心部件,但我没想到的是,这次阅读经历不仅让我重新认识了CPU,更让我对学习和探索有了全新的理解。书中首先从CPU的定义和历史讲起,逐步引导读者走进CPU......