大家好,欢迎来到IT知识分享网。
Thinking about C++
C++ 为什么这么复杂
C++ 的复杂和Bjarne Stroustrup 的理念有关系,C++ 是他发明和不断推动向前发展的,对于C++ 要满足搞性能的要求,会对内存资源要强烈的管理和优化需求,和java 的虚拟机不一样,它是直接可以操作内存,C++ 定义的很多特性如移动构造函数就是为了满足性能的需要,坚决的执行对内存的管理。
而作为普通的语言,比如python 类的脚本语言,他没有这样的执着,至少没有这么强烈,python的创作者是为了提高效率,python的观念是所有的东西都对象,包括内置类型,表达式,函数,以及组织脚本的模块等等。你用dir 来查看,就会看到一堆的属性。python解决性能的问题,依赖python 和C等语言的相同性,将性能放在库里边,通过C或C++进行封装,在python世界里边调用。
说白了python 的性能要求可以让C或C++帮他解决,但是C++ 的性能要求,只能通过自己解决。 我们可以想如果C++ 没有这样的性能表现,还有这么多的概念设计,应该早就被淘汰掉了。
C++ 是在追求性能的同时也在通过OOP和GP,让程序员设计的更加简洁。 高效、简洁可能就是C++追求的核心。
但是高效、简洁的C++代码,依赖的OOP和GP这样的基础,通过OOP的对象、继承关系,让代码在类内部完成分解。这就对程序员的要求非常高,另外要求程序员的表达式一致的,稳定的。 但是这些在小项目比如一些应用类的APP以及一些商业公司开发的app中可以实现。或者再某些支持的库、驱动、插件中也可以实现。但是在基本的OS,这个就不太适用。因为操作系统和APP或者lib是两个完全不同量级的东西。操作系统面对内存管理、进程调度对时空要求极为苛刻的需求,需要一种表达更为清晰、直接、同样高效的语言,这种语言绝对不是抽象过得。 它需要让更多的基础程序员能够更好的理解它,使用它。C往往来说是更合适的。
Chapter Getting Started
Entering an End-of-File from the Keyboard
Windows: Ctrl+z
Linux/Unix : Ctrl+d
Chaper 2 Variable and Basic Type
变量
变量的定义
int i,*p;
初始化
变量初始化在C++中是一个异常复杂的问题。 它涉及一般的初始化规则,比如在不同作用域下的初始化规则。如果初始化的是一个类对象,则涉及类的构造过程。
类的构造函数是类的一个核心概念,也是设计类的一个核心工作。根据不同的使用场景分为默认构造函数,copy构造函数,赋值运算符,移动构造函数,移动运算符,析构函数。 C++之所以复杂就是因为他对于资源分配定义的苛刻要求,比如移动构造函数,是为了解决copy构造在某些场景下的性能开销问题。
当一个对象创建的时候获得了初值,我们就说它初始化了。 通俗来讲,初始化发生在创建时,对象的数据获得过程。而赋值,是在变量使用过程中,存在的一次存储数据的再构造。
这在类的定义中区分尤其明显,默认构造函数是用来完成初始创建的成员赋值,而copy 构造函数是完成类对象使用过程中的成员再赋值。
列表初始化
int unit_sold = 0; int unit_sold = {0}; int unit_sold{0}; int unit_sold(0);
- 内置类型,它的值由定义的位置决定。定义在任何函数体之外,初始化为0。
- 内置类型,定义在函数体之内,不被初始化。表示未定义。
- 自定义类型,比如类,由自身规则定义。
未初始化变量
未初始化变量含有一个不确定的值,使用未初始化的值是一个错误的编程方式,并且很难调试。
倡导变量在定义的时候进行初始化,尤其是函数内。 因为在类内,函数外定义的数据成员,一般在构造函数的初始化列表中完成初始化,也可以在定义数据成员的时候完成初始化。
变量声明和定义之间的关系
什么是修饰符, 修饰符是用来修饰要声明的变量而不是基本数据类型。
int i=1024, *p = &i, &r = i;
指针
int* p; 和 int p; 是一样的,只是C++ 语法都可以,都表示p 是一个int 类型的指针, 修饰的是p 而不是 int.
const 限定符
const int bufferSize=512;
- 默认状态下,const 对象在本文件内有效。
当以编译方式初始化定义一个const 对象时,例如 const int bufSize = 512; 编译器将在编译过程中把用到这个变量的地方都替换成 512. 为了执行上述替换,编译器需要知道变量的初始值。如果程序包含多个文件,则每个用了 const对象的文件都必须得能访问到它的初始值才行。要做到这一点,就必须在每一个用到变量的的文件中都有对它的定义。为了支持这种用法,同时避免对一个变量的重复定义,默认情况下,const 对象被设定为仅在文件内有效。
const的引用
const int &ri = ci;
指针和const
所谓指向常量的指针或引用,不过是指针或引用 “自以为是”,他们觉得自己指向了常量,所以自觉(const)不去改变所指对象的值。
- const 指针
指针是对象而引用不是,因此就像其他对象类型一样,允许把指针本身定义为常量。常量指针(const pointer)必须初始化,而且一旦初始化完成,它的值(和指向变量的关系)就不能再改变了。
int errNumber = 0;
int *const curErr = &errNumber;
== 指针本身是一个常量并不意味着不能通过指针修改所指对象的值,能够这样做完全依赖对象的类型。==
顶层const
int i =0 ; int *const p1 = &i; //顶层const,表示这个指针是一个常量,不能改变p1. const int c1=42; //顶层const ,c1 不能修改。 const int *p2 = &ci; //底层const ,不能通过 p2 修改 c1 ,但是 p2的值可以修改。 const int *const p3 = p2; //靠右是一个顶层const ,用来说明 p3 不能修改指向p2的这个关系。 左边const 是一个底层const ,用来说明不能通过 p3来修改 所指向变量的值。 const int &r = c1; //用于声明引用的const 都是底层const. 表示不能通过 r修改c1.
int *p=p3; //错误 ,p3 包含底层const 定义,而p 没有 p2 = p3 ```//ok, p3和p2都是底层const p2 = &i; // 底层可以转化,非常量可以转化成常量 int &r = c1; //error ,普通的int& 不能绑定到 int 常量上。 const int &r2 = c1; //正确,const int& 可以绑定到一个普通的i;
Const 关于指针使用
const int *p1; // p1 is a non-const pointer and points to a const int int * const p2; // p2 is a const pointer and points to a non-const int const int * const p3; // p3 is a const pointer and points to a const it const int *pa1[10]; // pa1 is an array and contains 10 non-const pointer point to a const int int * const pa2[10]; // pa2 is an array and contains 10 const pointer point to a non-const int const int (* p4)[10]; // p4 is a non-const pointer and points to an array contains 10 const int const int (*pf)(); // pf is a non-const pointer and points to a function which has no arguments and returns a const int
constexpr
constexpr 是一种很强的约束,更好地保证程序的正确语义不被破坏。编译器可以在编译期对constexpr的代码进行非常大的优化,比如将用到的constexpr表达式都直接替换成终结果等。相比宏来说,没有额外的开销,但更安全可靠。
Identifier
convention
Scope of a Name
Compound Types
Reference
Pointers
A pointer is a compound type that “points to” another type. Like references, pointers are used for indirect access to other objects. Unlike a reference, a pointer is an object in its own right. Pointers can be assigned and copied; a single pointer can point to several different objects over its lifetime. Unlike a reference, a pointer need not be initialized at the time it is defined. Like other built-in types, pointers defined at block scope have undefined value if they are not initialized.
void* pointers
the type void* is a special pointer type that can hold the address of any object. like any other pointer ,a void* pointers holds an address, but the type of the object at the address is unknown.
Understanding Compound Type declarations
int* p1,p2; //p1 is pointer to int, p2 is an int.
Refer to Pointers
int i = 42; int *p; //p is pointer to int int *&r=p; // r is reference to p r = &i; // make p point to i *r = 0; // change i
The easiest way to understand the type of r is to read the definition right to left.
Chaper 3 String,Vector,Array
String
reference
- 初始化方法:
- string 对象上的操作:
cctyp 中定义的针对char的操作
如何改变字符串中的某些值,比较推荐的做法:
这个时候auto 声明的是一个引用。
string s("hello world"); for (auto &c :s) c=touppoer(c) cout << s <<endl; HELLO WORLD
使用下边进行迭代
for (decltype(s.size()) index =0; index != s.size() && !isspace(s[index]); ++ index) s[index] = toupper(s[index]);
vector
reference
- 引用
#include <vector>
- 初始化vector
- 相关操作
迭代器
- 迭代器相关操作
for(auto it=s.begin(); it!=s.end() && !isspace(*it);++it) *it = toupper(*it)
数组
数组和 Array 是两个概念,Array 是C++ 的一种容器, 而数组是C++ 或C 中一种基本的数据结构。
函数
- 函数在退出的时候做的事情很多,可能涉及到内部定义变量的资源释放。 不是简单的退出那么简单。 甚至,在函数内,某个作用域”{}”退出的时候,也会有资源释放,这些无疑为读汇编代码增加了难度。
Chaptor 7 Class
类的基本思想是数据的抽象和封装。数据抽象是一种依赖(interface) 和 实现(implementation)的分离编程技术。类的接口包括用户所能执行的操作,类的实现则包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数。
程序员常把运行其程序的人称作user。类似的,类的设计者也是为了其用户设计并实现一个类的人,类的用户是程序员。
在做类的封装中需要考虑的基本因素:
- 接口,对外需要提供哪些接口,方便user使用,user 不光是直接对类进行初始化使用的变量,还有可能是继承的派生类,这些派生类可以使用的接口。 常规来讲,类一般不会对数据成员进行开放,数据成员会通过构造函数进行初始化,或者类内声明的时候直接进行初始化。开放的一般都是一些函数,这些函数包括构造函数和user需要使用的功能函数,里边涉及到功能的定义。
- 访问控制,也就是封装,这个和接口分不开,需要考虑需要开放哪些接口,如果涉及菱形继承,就需要考虑如果对初始类进行存抽象,全部抽象成接口类,方便后边进行继承。也要考虑动态绑定如何设计。
- 资源,类需要使用的资源。一般来说不进行直接的内存方便管理,对性能要求也没有那么搞,可以通过关联和非关联容器对资源进行管理。 但是还是需要考虑这些资源的构造和释放问题,具体使用的是值类型,还是共享类型。通过资源考虑,完成copy构造函数,赋值运算符,以及移动构造函数,和移动运算符、析构函数的设计。
- 运算符重载,是否需要重载某些运算符,包括+,-等运算符,如果需要,需要进行这类函数的设计。
总之,类的设计,需要考虑的东西比较多,不光要向普通函数一样考虑算法,另外还要考虑如何使用和如何派生的问题,如果要掌握好,就要对常用的设计模式进行研究。
C++ Primer 针对类,涉及五个章节。
- 类
- copy 控制
- 操作符重载与类型转换
- 面向对象设计
- 大型程序中的多重继承和虚继承。
由于是理解,不准备按照章节逐步来,主要是针对类中的具体概念进行深入理解。
this指针
this 指针不是C++中特有的,python 中对应的是 self。是用来简化类作用域中对成员的引用。 默认情况下,它描述的是类实例化的对象本身,通过this可以对已经实例化的数据成员进行引用,当然包括成员函数。
this 指针默认是非常量的,也就是可以通过this指针,修改普通成员变量。 可以通过 成员函数后缀 const 来限定 。 如:
std::string isbn() const { return bookNo; }
向这样使用的成员函数,叫常量成员函数。
- 编译器 先编译类的成员声明,在编译成员函数实现。
构造函数
每个类都分别定义他的对象被初始化的方式,类通过一个或多个特殊的成员函数来控制其对象的初始化过程。这些函数叫做构造函数。构造函数的任务是初始化类对象的数据成员,无论何时,只要类的对象被创建,就会执行构造函数。
构造函数的名字和类相同,和其他函数不一样的是,构造函数没有返回类型;除此之外,类似其他函数,构造函数也有一个(可能为空的)参数列表和一个(可能空的)函数体。
不同于其他函数,构造函数不能被声明成const函数。 因为当我们创建一个类的const对象时,直到构造函数执行完成,对象才能真正取得其“常量”属性。
如果没有定义构造函数,类通过一个特殊的构造函数来控制默认初始化过程,这个函数叫做默认构造函数(default constructor). 默认构造函数无需任何实参。
编译器创建的构造函数又被称为合成的默认构造函数。对于大多数的类来说,这个合成的默认构造函数按照下边规则构造:
- 如果存在类型的初始化值,用来初始化。
- 否则,默认初始化该成员。
经验总结
namespace
头文件中的代码不应该包含using 声明,因为 头文件会被其他头文件和源文件引用,到了源文件中,你不知道到底包含了哪些东西, 不可控。
关于使用c头文件
vector 迭代器失效
虽然 vector 对象可以动态的增长,但是也会有一些副作用,已知的一个限制是任何一种可能改变vector 容量的操作,可能会让正在使用的iter 迭代器 失效。因为内存可能重新分配一个新的区域。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/136060.html