c++ 学习笔记
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了c++ 学习笔记,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含17160字,纯文字阅读大概需要25分钟。
内容图文
![c++ 学习笔记](/upload/InfoBanner/zyjiaocheng/640/9567a0a2f47e45dd897f323eee286ea7.jpg)
C++
更新与20200305 22:50
引用与指针的区别
- 指针有多级,引用没有多级
- 指针可以为null,但是引用不可以
- 在加法运算符的方面
int *p;
int &q;
p ++;
q ++;
两者概念是不同的,p++指的是地址的增加,q++指的是引用的值的增加
- 指针在使用中可以指向其他对象,但是引用只能是一个对象的引用,不能被改变
string s1("hello");
string s2("world");
string& rs = s1;
string *ps = &s1;
rs = s2;//惊了,现在s1变成了world
cout << s1 << endl;
ps = &s2;
- 指针可以不初始化,引用必须被初始化
int &p//declared as reference but not initialized
比如在实现operator[]这个运算符的时候,可以使用reference来实现(goto 后文的bitwise const绕开问题,具体就是operator[]返回对应的&,然后这样并不违反const function的bitwise const原则,你返回的reference可以被修改)
关键词 explicit
explicit解决隐式类型转换带来的问题
举个例子
template<class T>
class Array {
public:
Array(int size);
T& operator[](int index);
}
bool operator== (cosnt Array<int> &a, const Array<int> &b);
Array<int>a1(10), b1(10);
for(int i = 0; i < 10; i ++){
if(a1 == b1[i]){//会被隐式转换为a1 == static_cast< Array<int> >(b[i])
//do something
}
}
++ 与 -- 前置与后置
- 重载函数以其参数类型区分彼此,后置型的重载与前置型的重载区别在于,后置的多个变量(int)
operator++()
operator++(int)//后置型
operator--()
operator--(int)//后置型
//举个例子
UPInt& UPInt::operator++(){
*this += 1; //累加
return *this;//取出
}
const UPInt UPInt::operator++(int){
UPInt oldValue = *this;//取出
++(*this); //累加
return oldValue;
}
UPInt i;
i ++++;//这样的话i只加1次,直觉上肯定是错的
//等价为i.operator++(0).operator++(0)
前置型返回references,后置型返回const
c++ 四种转型操作符
- static_cast: int->double,也限制了一些奇奇怪怪的操作,比如将struct->int,double->pointer,也不能移除表达式的常量性
举个例子:
int a = 1.0
static_cast<double>(a);
- const_cast:改变表达式中的常量性(constness)或变易性(volatileness),如果用于其他用途,其他转型动作会被拒绝
void update(SpecialWidget *psw);
SpecialWidget sw;
const SpecialWidget& csw = sw;
update(const_cast<SpecialWidget*>(&csw));
const_cast无法使用cast down(继承体系的向下转型动作)
- dynamic_cast:用来执行继承体系中“安全的向下转型或跨系转型动作”,利用dynamic_cast将“指向base class objects的pointers或references"转为"derived class objects的pointers或references",如果转型失败,pointers对应的是null,reference对应的就是exception(异常)
void update(SpecialWidget *psw);
widget *pw;
update(dynamic_cast<SpecialWidge*>(pw));//如果转型成功,会返回一个对应的指针,如果转型失败那么会返回一个对应的null指针
void updateViaRef(Specualwidget &rsw);
updateViaRef(dynamic_cast<SpecialWidge&>(*pw));//如果转型成功,会对应返回一个reference,如果失败的话,会返回一个对应的exception,即异常
- reinterpret_cast:不具有移植性,最常用的用途是“指针的转换”
确定对象被使用前先被初始化
如果构造函数没有给出对应的初值,那么他会调用default函数来进行初始赋值
即进行一次default + copy assignment 单只调用一次copy进行构造 是较为高效的是这样的话
const
const或者references进行修饰的变量,就一定需要初值,因为不允许进行赋值
成员初始化次序是固定的,从base classes更遭遇derived classes被初始化,class成员变量的初始化顺序
取决于声明的顺序
对“定义于不同的编译单元内的non-local static对象”的初始化次序并无明确定义,解决这个问题是非常困难的
对于这种方法 我们可以使用reference-returning,初始化一个local static对象,在第二行返回他,来解决初始化
次序并无明确定义的问题
感觉这里讲得太抽象了,举个例子
class FileSystem{...};
FileSystem & tfs(){
static FileSystem fs;//定义并初始化一个static对象
return fs;
}
重载&& || ,
- c++ 与 c 处理真假表达式采用“鄹死式”评估方法,如果重载后那么“函数调用式语义”会代替原来的
- ,也可以被重载,那么,左边会被先评估,右边为返回的值
你可能想支持这样的性质,但是你无法保证左侧表达式一定比右侧表达式更早被评估
不能重载的运算符如下:
. .* :: ?: new delete sizeof typeid 四个转换运算符
const与#define
- 编译器处理的方法不同,#define是在预处理阶段展开的,const常量是在编译运行阶段使用的,《effective c++》中的说法是#define不被视为语言的一部分
举个例子:
#define a 1.653
//有可能存在a没进入记号表(symbol table),但是你使用了a,那么编译器就会报错,错误消息不会提示是a错误,而是提示1.653,然后你debug就会相对困难
- 类的安全性检查,#define没有类型检查,仅仅是展开而已,const有编译类型,在编译时会进行检查。
define不会分配内存,define宏仅仅是展开,有多少个地方就展开多少次,const定义时,会在内存中分配,《effective c++》中的说法是使用常量可能比使用#define导致更小的码
- 赋值时的空间分配,const可以减少空间,避免不必要的空间分配,
#define PI 3.14159 //常量宏
const doulbe Pi=3.14159; //此时并未将Pi放入ROM中 ......
double i=Pi; //此时为Pi分配内存,以后不再分配!
double I=PI; //编译期间进行宏替换,分配内存
double j=Pi; //没有内存分配
double J=PI; //再进行宏替换,又一次分配内存!
const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而 #define定义的常量在内存中有若干个拷贝。
- 编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
- 在类中 #define是不可以被封装的,const可以被封装,也就是说没有所谓的private #define
- 取一个const空间是合法的,取一个#define的空间一般是不合法的
- 如果使用#define来实现宏,像函数但是不会招致函数调用带来的额外开销,但是宏会出现一些问题,对于形似函数的宏,最好使用inline函数来替换#define ---《effective c++》
使用宏定义求结构体成员偏移量
#define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE*)0) -> MEMBER)
/*
(TYPE*)0 将0转型成TYPE类型指针
((TYPE*)0->MEMBER) 访问结构体中的成员
&((TYPE*)0->MEMBER) 取出数据成员地址,也就是相对于0的偏移量
(size_t) &((TYPE*)0->MEMBER) 将结果转成size_t类型
*/
new与malloc的区别
- 申请的内存所在位置不同,malloc是从自由存储区上分配内存,new是从堆分配内存
- 返回内存的安全性,new返回的是对象类型的指针, malloc返回的是void*,需要通过强制类型转换来进行转换为我们需要的类型,new是类型安全的操作符,类型安全的代码不会试图调用未授权的内存空间
- 内存失败分配的返回值,new内存失败返回的是bac_alloc,不会返回null;而malloc返回NULL
- 是否需要指定内存大小,使用new操作的话无需指定内存块的大小,而malloc则需要显式的指定内存的大小
- new/delete会调用对象的构造/析构函数,malloc不会
- 对数组的处理,new[]/delete[]配套使用,malloc则没有专门对于数组的处理
- new/delete的实现可以基于malloc/free,而malloc的实现不可以去调用new
- new/delete可以被重载,malloc/free不允许重载
- malloc在运行过程中发现内存不足,可以使用realloc函数进行内存重新分配实现内存的扩充。new则不行
- 在operator new抛出异常以反映一个未获得满足的需求之前,它会先调用一个用户指定的错误处理函数,这就是new-handler。new_handler是一个指针类型:指向了一个没有参数没有返回值的函数,即为错误处理函数。为了指定错误处理函数,客户需要调用set_new_handler,这是一个声明于的一个标准库函数:set_new_handler的参数为new_handler指针,指向了operator new 无法分配足够内存时该调用的函数。其返回值也是个指针,指向set_new_handler被调用前正在执行(但马上就要发生替换)的那个new_handler函数。
对于malloc,客户并不能够去编程决定内存不足以分配时要干什么事,只能看着malloc返回NULL。
new 的不同意义
string *ps = new string("dieowjoe");
- 实际上使用的是new operator 这个操作符是语言内建的,是不可以被重载的(1)他分配足够的内存来放置对象(2)他调用对象的构造函数constructor
- operator new 函数声明通常如下
void * operator new(size_t size);
//你可以以任意的形式来重载operator new,但是得保证第一个参数为size_t
3.从第一点看过来的具体实现
void *memory = operator new(sizeof(string));
call string::string("dieowjoe") on *memory;//然而这一步设计到调用一个constructor
//作为一个程序员我居然没有权利调用这一步,干
string *ps = static_cast<string*>(memory);
- Placement new,这东西可以再分配好的原始内存上构建对象
new (buffer) Widget(WidgetSize)//more effective c++ 条款8
//作为new operator的隐式调用之一
//这一点,笔记没有做好,想详细了解可以自行看 more effective c++ 条款8
针对数组的new数组的内存分配工作,不再由operator new来负责,而是由一个数组版的函数operator new[]来负责
而new operator针对array版本相对的会为每个对象调用一次constructor,默认调用default constructordelete 的不同意义
- operator delete跟delete operator类比于operator new跟new operator
delete ps;
//等同于下面
ps->~strong();
operator delete(ps);
- 使用operator new获得的内存应该是用operator delete来进行回收,不可以使用new operator 与 delete operator
例如如下
void *buffer = operator new(50 * sizeof(char));
operator delete(buffer);
//这种行为相当于在c中调用malloc free
- 使用placement new不应该使用operator delete或者delete operator来进行回收
void freeShared(void *memory);
pw ->~widget();
freeShared(pw);
//具体可以参考more effectie c++ 条款8
operator delete[]释放内存,会调用destructor
const
- 变量
- 指针(顶层:声明常量指针 char const p; 底层:声明指向常量的指针 const char p),const出现在*号的左边,表示被指物为常量,如果出现在*号的右边那么指指针为常量,如果出现在*号的两边那么指的是被指物与指针都是常量
- 函数参数(引用、指针)
例子
void f1(const weight * p)
void f1(weight const * p)
这两种写法都是相同的
- 函数返回值(常量)
- 成员函数(不能修改成员变量)
const 放在成员函数的前面,指的是返回值为const
const 放在成员函数的后面,指的是隐藏*this指针(遵守bitwise const原则)
两种说法:
- 成员函数只有在不改变对象之任何成员变量时才可以说是const (bitwise const)
bitwise存在可以绕开编译的问题,
举例
class CTextBlock{
public:
char & operator[](std::size_t position) const//bitwise const 声明
{return pText[position];}
private:
char * pText;
};
const CTextBlock cctb("hello");
char * c = &cctb[0];
*c = 'J';
2.一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不出的情况下才得如此
使用const成员函数遵守bitwise const声明,当你使用const函数想声明变量,那么该如何操作呢?
使用对用的命名mutable(可变的)利用mutable释放掉non-static成员变量的bitwise constness约束
const成员函数可用来处理取得的const对象
non-const operator[]调用自己的兄弟cosnt operator[]这样导致递归调用自己
如何在const函数修改成员变量的值?
- 使用mutable关键字
造一个假的this去操作成员变量
void Class1::func1() const { //声明一个指针指向this所指对象,并先将这个对象的常量性转型成const Class1 * const fakeClass1 = const_cast<Class1* const>(this); //使用造出来的const指针,去修改成员变量 fakeClass1->_value = 1; }
default constructor(默认构造函数)
存在的意义:“合理的无中生有对象”
- 如果没有default constructor 那么产生数组的话,会失败。
存在两种解决方案:1.提供必要的参数。2.使用指针数组指向
没有default constructor的class容易造成的问题是 template可能会造成问题 (具体参照more effective c++ 条款4) Virtual base class如果没有default constructor那么他的derived class objects都必须了解virtual base class的变量含义,不然会发生错误
static
- 全局变量\函数(隐藏,本文件可见)
同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。 - 局部变量(保持变量内容的持久,函数调用)
static的第二个作用是保持变量内容的持久。(static变量中的记忆功能和全局生存期)
存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。
共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。虽然这种用法不常见
PS:如果作为static局部变量在函数内定义,它的生存期为整个源程序,但是其作用域仍与自动变量相同,只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。 - static的第三个作用是默认初始化为0(static变量)
- 成员函数(没有this指针,无法访问成员变量)
成员变量(属于类的)
模板全特化和偏特化
- 全特化:全特化就是限定死模板实现的具体类型
- 偏特化(局部特化):偏特化就是如果这个模板有多个类型,那么只限定其中的一部分。
多态
多态如何实现,内存布局
- 继承的最重要的性质之一:可以通过“指向base class objects”的pointers或references,来操作derived class objects,我们可以称这样的pointers或references的行为是多态的。
(1)使用derived class objects的pointers或者references来操作数组的话,会造成下述情况
一般不使用多态的方式来处理数组,因为arr[i]其实是一个“指针算数表达式”的简写,这么说吧,arr[i] == (arr + i)
本质是arr+sizeof()i,那么因为多态的话,使用derived class objects来传递的话,那么就会出现按照arr+sizeof(base class objects)*i,那么不能得到正确的答案了
(2)使用base class objects操作对应来delete[] derived class objects,这种情况主要是因为base class objects的析构函数不是virtual function
多态分为两种:编译时多态和运行时多态
- 编译时多态
编译时多态由函数重载,以及模板实现
运行时多态
运行时多态由虚指针和虚表实现
堆和栈的区别
内联函数和宏定义函数的区别
- 运行方面:#define生成的宏定义函数不可以被调试,inline的函数在运行时可以调试
- 编译器会对内联函数的参数类型做安全监测或自动类型转换,而宏定义则不会; (安全性方面)
- inline的内联函数可以访问类的成员变量,#define生成的宏定义不能访问类的成员变量;
- 在类中声明同时定义的成员函数,自动转化为内联函数。
C++ 的“函数内联”是如何工作的。
对于任何内联函数,编译器在符号表里放入函数的声明(包括名字、参数类型、返回值类型)。如果编译器没有发现内联函数存在错误,
那么该函数的代码也被放入符号表里。在调用一个内联函数时,编译器首先检查调用是否正确(进行类型安全检查,或者进行自动类型转换,当然对所有的函数都一样)。如果正确,内联函数的代码就会直接替换函数调用,于是省去了函数调用的开销。这个过程与预处理有显著的不同,因为预处理器不能进行类型安全检查,或者进行自动类型转换。假如内联函数是成员函数,对象的地址(this)会被放在合适的地方,这也是预处理器办不到的。
C++ 语言的函数内联机制既具备宏代码的效率,又增加了安全性,而且可以自由操作类的数据成员。所以在C++ 程序中,应该用内联函数取代所有宏代码,“断言assert”恐怕是唯一的例外。assert是仅在Debug版本起作用的宏,它用于检查“不应该”发生的情况。为了不在程序的Debug版本和Release版本引起差别,assert不应该产生任何副作用。如果assert是函数,由于函数调用会引起内存、代码的变动,那么将导致Debug版本与Release版本存在差异。所以assert不是函数,而是宏。
内存泄露以及如何解决
使用智能指针管理资源
RAII(Resource Acquisition Is Initialization)资源获取就是初始化
利用destructors避免泄露资源
合理的想法是使用一个abstract base class,再派生出对应的concrete classes,由虚函数来负责对不同种类的必要处理动作
举个例子:
void processAdoptions(istream& dataSource){
while(dataSource){
ALA *pa = readALA(dataSource);
pa->processAdoption();
delete pa;
}
}
//假设pa->processAdoption抛出异常exception,那么就意味着接下来的语句都会被跳过,就会内存泄露
//那么可以使用下面的方式来进行修改
void processAdoptions(istream& dataSource){
while(dataSource){
ALA *pa = readALA(dataSource);
try{
pa->processAdoption();
}
catch(){
delete pa;
throw;
}
delete pa;
}
}
//当然是又长又臭 烦死了
//当然我么使用智能指针就可以了,引用计数归0就自动销毁就可以了
//虽然auto_ptr 在c++ 11 新规范 被销毁了,但是我们还是要学习一下的
template<class T>
class auto_ptr{
public:
auto_ptr(T *p = 0): ptr(p){}
~auto_ptr(){ delete ptr;}
private:
T *prt;
}
void processAdoptions(istream& dataSource){
while(dataSource){
auto_ptr<ALA> pa(readALA(dataSource));
pa->processAdoption();
delete pa;
}
}
auto_ptr的核心思想:以一个对象存放“必须自动释放的资源”,并依赖于改对象的destructor释放
在这样的设计确实可以实现在pa->processAdoption()抛出异常的时候,保证内存不会泄露
但是如果抛出异常是在constructor或者deconstructor过程中呢?那么要如何处理呢?请看下文
constructor 处理异常抛出导致的内存泄露(请配合more effective c++ 条款10阅读此点)
代码参考more effective c++ 条款10(太长不想抄)
- 异常可能会因为operator new分配不到内存或者各种乱七八糟的错误,导致在constructor的时候出现问题
抛出异常,那么会调用deconstructor吗?
答案显然是不会,因为deconstructor只能析构已经构造完成的对象
问题在于c++ 不允许对构造未完成的class调用deconstructor 针对于一些奇特的结构,theImage,theaudioclip为常量指针,那么就不能先初始化,在赋值了
所以我们在初始化列表的时候使用对应的表达式,就没法使用try catch来捕获异常,(try catch属于语句)
结合书上看,我们可以得知解决的方案使用一个内置的private函数来返回对应需要的指针,如果失败就抛出异常
缺点也很明显:本来只需要一个structor实现的功能,却散布在很多函数中 当然使用auto_ptr 显然更为优秀
看完这个条款,兜兜转转又回到auto_ptrdeconstructor 处理异常抛出导致的内存泄露(请配合more effective c++ 条款11阅读此点)
- 存在一个有意思的东西,如果在调用destructor的时候抛出了对应的异常,恰巧还有个异常处于作用状态,然后你的程序就会
被terminate函数直接干掉,(more effective c++描述了一个记日志的析构函数,如果直接干掉会导致信息并没有被写入日志) 如果让一个异常从destructor被抛出异常,那么严重的话会导致程序的结束,轻的话,会导致destructor函数执行不全
智能指针,unique_ptr怎么实现的
unique_ptr不能拷贝,如何实现
- 针对于unique_ptr函数不能拷贝 可以使用将其的拷贝构造函数,赋值运算符声明为private
- 或者令其拷贝构造函数,赋值运算符=delete
深拷贝和浅拷贝
c++11
右值引用
lamda表达式
malloc的实现方案
malloc的实现方案:
1)malloc 函数的实质是它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。
2)调用 malloc()函数时,它沿着连接表寻找一个大到足以满足用户请求所需要的内存块。 然后,将该内存块一分为二(一块的大小与用户申请的大小相等,另一块的大小就是剩下来的字节)。 接下来,将分配给用户的那块内存存储区域传给用户,并将剩下的那块(如果有的话)返回到连接表上。
3)调用 free 函数时,它将用户释放的内存块连接到空闲链表上。
4)到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段, 那么空闲链表上可能没有可以满足用户要求的片段了。于是,malloc()函数请求延时,并开始在空闲链表上检查各内存片段,对它们进行内存整理,将相邻的小空闲块合并成较大的内存块。
operator->()重载
1.如果point是指针,则按照内置的箭头运算符去处理。表达式等价于(*point).member。首先解引用该指针,然后从所得的对象中获取指定的成员。如果point所指的类没有名为member的成员,则编译器报错。
2.如果point是一个定义了operator->() 的类对象,则point->member等价于point.operator->() ->member。其中,如果operator->()的返回结果是一个指针,则转第1步;如果返回结果仍然是一个对象,且该对象本身也重载了operator->(),则重复调用第2步,否则编译器报错。最终,过程要么结束在第一步,要么无限递归,要么报错。
内容总结
以上是互联网集市为您收集整理的c++ 学习笔记全部内容,希望文章能够帮你解决c++ 学习笔记所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。