C++知识点总结(上)《C++ Primer》

1 篇文章 0 订阅
1 篇文章 0 订阅

C++知识点总结(上)《C++ Primer》


参考教材:《C++ Primer》(Stanley B. Lippman 第五版)

2020.9.6

By 盒子先生KingO

一、编译流程

源代码(source coprede)→预处理器(processor)→编译器(compiler)→汇编程序(assembler)→目标程序(object code)→链接器(Linker)→可执行程序(executables)
1、预处理
  • 读取C/C++源程序,对其中的伪指令(以#开头的指令)进行处理
  • 删除所有的注释
  • 添加行号和文件名标识
  • 保留所有的#pragma编译器指令
2、编译

将预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后,产生相应的汇编代码文件。

3、汇编

将编译完的汇编代码文件翻译成机器指令,并生成可重定位目标程序的.o文件,该文件为二进制文件,字节编码是机器指令。

4、链接

通过链接器将一个个目标文件(或许还会有库文件)链接在一起生成一个完整的可执行程序。

将生成的.obj文件与库文件.lib等文件链接,生成可执行文件(.exe文件)

二、常识

1、windows下
  • .dll:动态链接库,作为共享函数库的可执行文件
  • .obj: 对象文件,相当于源代码对应的二进制文件,未经重定位
  • .lib: 可理解为多个obj的集合,本质与.obj相同
2、linux下
  • .so:(share object)动态链接库,跟Windows平台类似
  • .o: 对象文件,相当于源代码对应的二进制文件
  • .a: 与.o类似,多个.o的集合
3、C和C++的一个区别

c++的编程思想是面向对象,而c的编程思想是面向过程

  • “自顶向下,逐步求精”的面向过程程序设计

    面向过程程序设计的思想即这样的一种解决思路 - 提出问题,分析问题的处理流程,将大问题分解为小问题,如果小问题比较复杂,就继续划分小问题为更小的问题,然后通过小模块一一解决小问题,最后再根据整个业务流程将这些小问题串在一起(调用函数),这样就达到了解决所有问题的目的
    在这里插入图片描述

    • 优点:
      • 程序结构简单(仅由顺序、选择、循环构成)
      • 分而治之,逐个击破
      • 自顶向下,逐步求精
    • 缺点:
      • 数据和操作往往是分离的(数据结构发生变化,操作的函数不得不重新改写)
      • 数据往往不具有封装性,变量暴露在全局
  • 万般皆对象:面向对象程序设计

    多态、继承、封装

    面向对象思想认为:现实世界是由对象组成的,无论大到一个国家还是小到一个原子,都是如此。并且对象都由两部分组成 - 描述对象状态或属性的数据(变量)以及描述对象行为或者功能的方法(函数)。并且与面向过程不同,面向对象是将数据和操作数据的函数紧密结合,共同构成对象来更加精确地描述现实世界,这是面向过程和面向对象两者最本质的区别。

在这里插入图片描述

  • 优点
    • 容易设计和实现
    • 复用设计和代码,开发效率和系统质量都得到了提高
    • 容易扩展
    • 安全性高

三、重要知识点

1、引用

引用(reference)为对象起了另外一个名字,引用类型引用(refers to)另外一种类型。

int ival = 1024;
int &refVal = ival;				//refVal 指向ival(是ival的另一个名字)
int &refVal2;					//报错:引用必须被初始化

定义引用时,程序把引用和它的初始值**绑定(bind)**在一起,而不是拷贝。引用即别名,并非对象

int &refVal3 = 10;        		//错误:引用类型的初始值必须是一个对象
double dval = 3.14;
int &refVal4 = dval;			//错误:此处引用类型的初始值必须是int型对象
2、指针

指针,本身是一个对象,允许赋值和拷贝,无需在定义时赋初值。

int ival = 42;
int *p = &ival;					//正确
double *pd1 = &ival				//错误:类型不匹配

空指针:nullptr

int *p1 = nullptr;				//C++新标准
//等价于
int *p1 = 0;

建议:初始化所有指针(如果不初始化,将拥有一个不确定的值)

int ival = 1024;
int *p = &ival;
int **ppi = π				//指向指针的指针

int i = 42;
int *p;
int *&r = p;					//指向指针的引用
3、const

const类型的对象不能改变其内容

默认状态下,const对象仅在文件内有效,如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern

//file_1.cc定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize = fcn();
//file_1.h头文件
extern const int bufSize;		//与file_1.cc中定义的bufSize是同一个

程序员常将**“对const的引用"简称为"常量引用”**

引用的类型必须与其所引用对象的类型一致,但有两种特殊情况。其中之一,常量引用时允许用任意的表达式作为初始值

int i = 42;
const int &r1 = i;			//允许将const int &绑定到一个普通int对象上
const int &r2 = 42;			//正确:r2是一个常量引用	
//reason: 编译器会创建一个临时量对象
//
const int r = 42;
const int &r2 = r;
//
const int &r3 = r1 * 2;		//正确:r3是一个常量引用
int &r4 = 42:				//错误
int &5 = r1*2;				//错误,非常量引用不可以这么干

常量指针(const pointer),必须初始化,一旦初始化完成,则值不能再改变(存放在指针中的地址),不变的是指针的本身而不是指向的那个值

int errNumb = 0;
int *const curErr = &errNumb;	//curErr将一直指向errNumb
const double pi = 3.14159;
const double *const pip = π	//pip是一个指向常量对象的常量指针
  • **顶层const:**指针本身是个常量
  • **底层const:**指针所指的对象是一个常量
4、static
  • 修饰普通变量,修改变量的存储区域和生命周期,使变量存储在静态区,在 main 函数运行前就分配了空间,如果有初始值就用初始值初始化它,如果没有初始值系统用默认值初始化它。
  • 修饰普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用。在多人开发项目时,为了防止与他人命名空间里的函数重名,可以将函数定位为 static。
  • 修饰成员变量,修饰成员变量使所有的对象只保存一个该变量,而且不需要生成对象就可以访问该成员。
  • 修饰成员函数,修饰成员函数使得不需要生成对象就可以访问该函数,但是在 static 函数内不能访问非静态成员。
5、this指针
  • this 指针是一个隐含于每一个非静态成员函数中的特殊指针。它指向调用该成员函数的那个对象

  • 当对一个对象调用成员函数时,编译程序先将对象的地址赋给 this 指针,然后调用成员函数,每次成员函数存取数据成员时,都隐式使用 this 指针。

  • 当一个成员函数被调用时,自动向它传递一个隐含的参数,该参数是一个指向这个成员函数所在的对象的指针。

  • this 指针被隐含地声明为: ClassName *const this,这意味着不能给 this 指针赋值;在 ClassName 类的 const 成员函数中,this 指针的类型为:const ClassName* const,这说明不能对 this 指针所指向的这种对象是不可修改的(即不能对这种对象的数据成员进行赋值操作);

  • this 并不是一个常规变量,而是个右值,所以不能取得 this 的地址(不能 &this)。

  • 在以下场景中,经常需要显式引用 this 指针:

    1. 为实现对象的链式引用;
    2. 为避免对同一对象进行赋值操作;
    3. 在实现一些数据结构时,如 list
6、using声明

得到命名空间中的成员 using namespace::name;

#include<iostream>
#include<string>
using std::cin;
using std::cout;
using std::string;

头文件中不应包含using声明

7、string
string s1; 						//默认初始化,s1是一个空字符串
string s2 = s1;					//s2是s1的副本
string s3 = "hello";			//s3是该字符串字面值的副本
//等价于
string s33("hello");
string s4(10,'C');				//S4的内容是cccccccccc

//读取未知数量的string对象
string word;
while(cin>>word)
    cout<<word<<endl;

//使用getline读取一整行
string line;
//每次读入一整行,直至到达文件末尾
while(getline(cin,line))
    cout<<line<<endl;

//每次读入一整行,遇到空行直接跳过
while(getline(cin,line))
    if(!line.empty())
        cout<<line<<endl;

//每次读入一整行,输出其中超过80个字符的行
while(getline(cin,line))
    if(line.size()>80)
        cout<<line<<endl;



string s = s1 + "," + "world" + '\n';

//处理每个字符
string str("some string");
for(auto c : str)
    cout<<c<<endl;

字面值和string对象相加

当把string对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符(+)的两侧的运算对象至少有一个是string:

string s2 = s1 + ",";			//正确:把一个string对象和一个字面值相加
string s3 = "hello"+",";		//错误:两个运算对象都不是string
string s4 = s1 + "," + "word";	//正确:每个加法运算符都有一个运算对象是string
//reason: (s1+",")+"word"
string s5 = "hello" + "," + s1; //错误:不能把字面值直接相加
//reason: ("hello"+",") + s1
8、vector

vector表示对象的集合,其中所有对象的类型都相同。集合中的每个对象都有一个与之对应的索引。 容器、类模板

//列表初始化vector对象
vector<string> articles = {"a","an","the"};
vector<string> v1{"a","an","the"};
vector<string> v1("a","an","the");   //错误

//push_back添加元素
vector<int> v2;						//空vector对象
for(int i = 0; i != 100; ++i)
    v2.push_back(i);
9、迭代器

访问string对象的字符或vector对象的元素,除了使用下标运算符外,还有一种更通用的机制——迭代器(iterator)。这些类型拥有名为beginend的成员。begin成员返回指向第一个元素的迭代器,end成员负责返回指向容器“尾元素的下一个位置(one past the end)”的迭代器(也就是本不存在)。如果容器为空,则begin和end返回的是同一个迭代器

string s("some string");
for(auto it = s.begin; it != s.end() && !isspace(*it); ++it)
    *it = toupper(*it);

泛型编程:C++程序员习惯性地使用!=,其原因和他们更愿意使用迭代器而非下标地原因一样:因为这种编程风格在标准库提供的所有容器上都有效。(for循环中使用!=而非<进行判断)

//使用迭代器完成二分搜索
auto beg = text.begin(),end = text.end();
auto mid = text.begin() + (end - beg) / 2;
while(mid != end && *mid != sought)
{
    if(sought < *mid)
        end = mid;
    else
        beg = mid + 1;
    mid = beg + (end - beg) / 2;
}
10、sizeof运算符

sizeof运算符返回一条表达式或一个类型名字所占的字节数,其所得值是一个size_t类型的常量表达式。其运算对象有两种形式: 1.sizeof(type)

2.sizeof expr

Sales_data data, *p;
sizeof(Sales_data);					//存储Sales_data类型的对象所占的空间大小
sizeof data;						//data的类型的大小,即sizeof(Sales_data)
sizeof data.revenue;				//Sales_data得revenue成员对应类型的大小
sizeof Sales_data::revenue;			//另一种获取revenue大小的方式

sizeof运算符的结果部分地依赖于其作用的类型:

  • 对char或者类型为char的表达式执行sizeof运算,结果得1
  • 对引用类型执行sizeof运算得到被引用对象所占空间的大小
  • 对指针执行sizeof运算得到指针本身所占空间的大小
  • 对解引用指针执行sizeof运算得到指针指向的对象所占空间的大小,指针不需有效
  • 对数据执行sizeof运算得到整个数组所占空间的大小,等价于对数组中所有的元素各执行一次sizeof运算并将所得结果求和。注意,sizeof运算不会把数组转换成指针来处理
  • 对string对象或vector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中元素占用了多少空间
11、try语句块和异常处理
  • throw表达式:异常检测部分使用throw表达式来表示它遇到了无法处理的问题
  • try语句块:异常处理部分使用try语句块处理异常。try语句块以关键字try开始,并以一个或多个catch子句结束。try语句块中代码抛出的异常通常会被某个catch子句处理。因为catch子句“处理”异常,所以它们也被称作异常处理代码
  • 异常类:用于在throw表达式和相关的catch子句之间传递异常的具体信息

throw表达式

if(item1.isbn() != item2.isbn())
    throw runtime_error("Data must refer to same ISBN");	//如果程序执行到这里,发生异常
															//异常类型为runtime_error的对象,抛出异常将终止当前的函															   //数,并把控制权转移给能处理该异常的代码

try语句块

通用语法形式:

try{
	program-statements             					//正常逻辑
} catch (exception-declaration){					//异常声明1
	handler-statements
} catch (exception-declaration){					//异常声明2
	handler-statements
} // ...

常见异常类

exception最常见的问题
runtime_error只有在运行时才能检测出的问题
range_error运行时错误:生成的结果超出了有意义的值域范围
overflow_error运行时错误:计算上溢
underflow_error运行时错误:计算下溢
logic_error程序逻辑错误
domain_error逻辑错误:参数对应的结果值不存在
invalid_argument逻辑错误:无效参数
length_error逻辑错误:试图创建一个超出该类型最大长度的对象
out_of_range逻辑错误:使用一个超出有效范围的值
12、函数

**内联函数:**可避免函数调用的开销

  • 相当于把内联函数里面的内容写在调用内联函数处;
  • 相当于不用执行进入函数的步骤,直接执行函数体;
  • 相当于宏,却比宏多了类型检查,真正具有函数特性;
  • 编译器一般不内联包含循环、递归、switch 等复杂操作的内联函数;
  • 在类声明中定义的函数,除了虚函数的其他函数都会自动隐式地当成内联函数。
// 声明1(加 inline,建议使用)
inline int functionName(int first, int second,...);

// 声明2(不加 inline)
int functionName(int first, int second,...);

// 定义
inline int functionName(int first, int second,...) {/****/};

// 类内定义,隐式内联
class A {
    int doA() { return 0; }         // 隐式内联
}

// 类外定义,需要显式内联
class A {
    int doA();
}
inline int A::doA() { return 0; }   // 需要显式内联

编译器对inline函数的处理步骤

  1. 将 inline 函数体复制到 inline 函数调用点处;
  2. 为所用 inline 函数中的局部变量分配内存空间;
  3. 将 inline 函数的的输入参数和返回值映射到调用方法的局部变量空间中;
  4. 如果 inline 函数有多个返回点,将其转变为 inline 函数代码块末尾的分支(使用 GOTO)。

优缺点

优点

  1. 内联函数同宏函数一样将在被调用处进行代码展开,省去了参数压栈、栈帧开辟与回收,结果返回等,从而提高程序运行速度。
  2. 内联函数相比宏函数来说,在代码展开时,会做安全检查或自动类型转换(同普通函数),而宏定义则不会。
  3. 在类中声明同时定义的成员函数,自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能。
  4. 内联函数在运行时可调试,而宏定义不可以。

缺点

  1. 代码膨胀。内联是以代码膨胀(复制)为代价,消除函数调用带来的开销。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
  2. inline 函数无法随着函数库升级而升级。inline函数的改变需要重新编译,不像 non-inline 可以直接链接。
  3. 是否内联,程序员不可控。内联函数只是对编译器的建议,是否对函数内联,决定权在于编译器。

**assert预处理宏:**所谓预处理宏其实是一个预处理变量,它的行为有点类似于内联函数。assert宏使用一个表达式作为它的条件:

assert(expr)

首先对expr求值,如果表达式为假(即0),assert输出信息并终止程序的执行,如果表达式为真(即非0),assert什么也不做。

**NDEBUG预处理变量: **assert的行为依赖于NDEBUG预处理变量的状态,如果定义了NDEBUG,则assert什么也不做。默认状态下没有定义NDEBUG,此时assert将执行运行时检查。

我们可以使用一个#define语句定义NDEBUG,从而关闭调试状态。

**C++编译器定义的静态数组:**方便用于程序调试

_ _ func _ _存放当前调试的函数的名字_
_ _ FILE _ _存放当前文件名的字符串字面值
_ _ LINE _ _存放当前行号的整型字面值
_ _ TIME _ _存放文件编译时间的字符串字面值
_ _ DATE _ _存放文件编译日期的字符串字面值

**函数指针:**函数指针指向函数而非对象

//比较两个string对象的长度
bool lengthCompare(const string &, const string &);

//声明一个可以指向该函数的指针
bool (*pf)(const string& , const string&);

pf = lengthCompare;						//pf指向名为lengthCompare的函数
pf = &lengthCompare;					//等价的赋值语句:取地址符是可选的

bool b1 = pf("hello", "goodbye");		//调用lengthCompare函数 
bool b2 = (*pf)("hello", "goodbye");	//一个等价的调用
13、union联合

联合(union)是一种节省空间的特殊的类,一个 union 可以有多个数据成员,但是在任意时刻只有一个数据成员可以有值。当某个成员被赋值后其他成员变为未定义状态。联合有如下特点:

  • 默认访问控制符为 public

  • 可以含有构造函数、析构函数

  • 不能含有引用类型的成员

  • 不能继承自其他类,不能作为基类

  • 不能含有虚函数

  • 匿名 union 在定义所在作用域可直接访问 union 成员

  • 匿名 union 不能包含 protected 成员或 private 成员

  • 全局匿名联合必须是静态(static)的

    #include<iostream>
    
    union UnionTest {
        UnionTest() : i(10) {};
        int i;
        double d;
    };
    
    static union {
        int i;
        double d;
    };
    
    int main() {
        UnionTest u;
    
        union {
            int i;
            double d;
        };
    
        std::cout << u.i << std::endl;  // 输出 UnionTest 联合的 10
    
        ::i = 20;
        std::cout << ::i << std::endl;  // 输出全局静态匿名联合的 20
    
        i = 30;
        std::cout << i << std::endl;    // 输出局部匿名联合的 30
    
        return 0;
    }
    
14、::范围解析运算符

分类:

  1. 全局作用域符(::name):用于类型名称(类、类成员、成员函数、变量等)前,表示作用域为全局命名空间
  2. 类作用域符(class::name):用于表示指定类型的作用域范围是具体某个类的
  3. 命名空间作用域符(namespace::name):用于表示指定类型的作用域范围是具体某个命名空间的
int count = 11;         // 全局(::)的 count

class A {
public:
	static int count;   // 类 A 的 count(A::count)
};
int A::count = 21;

void fun()
{
	int count = 31;     // 初始化局部的 count 为 31
	count = 32;         // 设置局部的 count 的值为 32
}

int main() {
	::count = 12;       // 测试 1:设置全局的 count 的值为 12

	A::count = 22;      // 测试 2:设置类 A 的 count 为 22

	fun();		        // 测试 3

	return 0;
}

14、::范围解析运算符

分类:

  1. 全局作用域符(::name):用于类型名称(类、类成员、成员函数、变量等)前,表示作用域为全局命名空间
  2. 类作用域符(class::name):用于表示指定类型的作用域范围是具体某个类的
  3. 命名空间作用域符(namespace::name):用于表示指定类型的作用域范围是具体某个命名空间的
int count = 11;         // 全局(::)的 count

class A {
public:
	static int count;   // 类 A 的 count(A::count)
};
int A::count = 21;

void fun()
{
	int count = 31;     // 初始化局部的 count 为 31
	count = 32;         // 设置局部的 count 的值为 32
}

int main() {
	::count = 12;       // 测试 1:设置全局的 count 的值为 12

	A::count = 22;      // 测试 2:设置类 A 的 count 为 22

	fun();		        // 测试 3

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值