c++ 笔记
2018-06-28 11:26:20 HelloZEX 阅读数 91

《Effective C++ 改善程序与设计的55个具体做法》笔记

一、让自己习惯C++

 

条款01:视C++为一个语言联邦

1.     C++由四个次语言组成:

2.     C语言

3.     Object-Oriented C++ 面向对象C++

4.     Template C++ 模板C++ C++的泛型编程部分

5.     STL 标准模板库

 

C++高效编程守则视状况而变化,取决于你使用C++的哪一部分。

 

条款02:尽量以constenuminline、替换#define

const, enum, inline 替换#define主要是两个维度。 一个是数据,一个是函数。

要注意一种情况:累的成员常量在类中应该在声明时加上static。

使用inline代替宏定义,这一点主要是说的函数宏定义。使用宏定义函数时,好处是省去函数调用的开销,缺点也很明显,比如定义结构不清晰,参数求值的副作用。

 

a.对于单纯常量,最好以const对象或者enums替换#defines;

b.对于形似函数的宏(macros),最好改用inline函数替换#defines;

 

条款03:尽可能使用const

如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自己是常量;如果两边都有,则被指物和指针两者都是常量。

const可以用来修饰 指针 / 迭代器 / 引用 / 参数 / 返回值 / 函数类型。

尽可能的使用const可以借助编译器检查出一些编译阶段的错误。

当const成员函数和非const成员函数有着实质等价的实现时,用非const版本调用const版本可以避免代码重复。

编译器强制实时【二进制常量】,例如const成员变量不会允许被赋值。 而【逻辑上的常量】需要自己根据实际情况来决定。

 

a.将某些东西声明为const可帮助编译器侦测出错误用法。const可以被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。

b.编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”(conceptual constness)。

c.当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可以避免代码重复。

 

条款04:确定对象被食用前已先被初始化

       内置类型成员变量的初始化、类对象成员的成员变量的初始化等,建议都使用初始化列表进行初始化,而非赋值。

       在舒适化列表的初始化顺序:基类优先、其他成员按照在类中声明的顺序。

 

a.为内置型对象进行手工初始化,因为C++不保证初始化它们。

b.构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。

c.为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象。

 

 

 

二、构造、析构、赋值运算

 

条款05:了解C++默默编写并调试哪些函数

带引用成员的类,如果不为其主动编写operator=,则对象之间的赋值操作是不能编译通过的;

带const成员的类,如果不为其编写operator=,则对象之间的赋值是不能通过编译的。

      

编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符,以及析构函数。

 

 

条款06:若不想使用编译器自动生成的函数,应该明确拒绝

 

根据条款05我们知道编译器会为类(在一定条件下)创建默认的构造、析构、拷贝构造、operator=函数,而当我们明确不需要编译器自作多情时,就该明确告诉编译器:别费劲了,老子有主了~   即把这些函数声明(可以只声明而不定义)为private的。 

当然这种方法并不保险,因为成员函数和friend函数仍然可以访问到这些函数,我们可以谨慎的不在成员函数和friend函数中对其进行调用(这一点,当只声明而不定义这些函数的时候,linker会帮助我们把不期望的调用找出来)。

另一种方法:

定义一个基类,该基类的作用是把不想让编译器自动生成的函数在该基类中声明为private的,然后让其他类继承该基类。

当客户端在我们的类没有主动定义一个这样的函数的时候去调用这些函数事,则编译器会自动生成对应的函数,而其【编译器生成版本】会尝试调用基类的对应兄弟而就会被决绝(private)。

 

为驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明为private并且不予实现。使用像Uncopyable这样的base class也是一种做法。

 

 

 条款07:为多台基类声明virtual析构函数

 

    任何一本C++语法教材上都会讲这一点(如果没讲,扔掉它),这么做到原因是可以让deletepBase操作能够正确的执行子类的析构函数。

需要说明的是当一个类不是用来当作基类或者不是用来在多态场景下使用时,就不要为这个类的析构函数声明为virtual的,因为这会额外给这类加大体积。

 

a.带多态性质的base class应该声明一个virtual析构函数,如果class带有任何virtual函数,他应该拥有体格virtual析构函数。

b.Classes的设计目的如果不是作为base class使用,或者不是为了具备多态性,就不应该声明virtual析构函数。

 

 

 

 

 

条款08:别让异常逃离析构函数

 

       a.别让析构函数中产生异常,如果非要产生则要么吞下(不传播),要么结束程序。如果某个操作可能会抛出异常,则应该让其在一个普通函数中执行,而非在析构函数中执行。

       b.如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(不应该是在析构函数)执行该操作。

 

条款09:绝不在构造和析构函数中调用virtual函数

【在基类构造期间,virtual函数不是virtual函数】,即如果在基类的构造函数中调用了virtual函数,则虽然该virtual函数在子类中不同的实现,则在基类的构造函数中调用的却是基类的virtual函数,如果该virtual函数在基类中没有实现则linkier会给出错误从而让我们有章可循;当如果该virtual虚函数在基类中有一个实现时,则该错误就很难被发现。

析构函数同理。


在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class。



。。。。。。。。。。。。。

c++笔记
2018-10-19 12:42:24 zzqgf69 阅读数 47

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

C++笔记
2016-05-19 17:26:32 mcfer 阅读数 1033

signedunsigned的转换:

①无符号转有符号:若是最高位为0,说明无符号没有超过有符号的范围,那么有符号数直接等于无符号数,若是无符号的最高位为1,那么将非符号位取补码,得到的数就是有符号位。

②有符号位转无符号位:若是最高位为0,说明有符号数为整数,那么无符号数就是有符号数,若是有符号数的最高位为1,那么将符号位取补码得到无符号数。

比如说对于

unsigned char uschar=233;

signed char schar=uschar;//那么schar=233-256=-23;

//

signed char schar=-23;

unsigned char uschar=schar;//那么schar=-23+256=223;

超过了就减,是负数就加

#define和typedef:(P61)

①  #define只是简单的宏替换

②  typedef则是声明一种新的类型

③  举个简单的例子。

#define int * p_int;

typedef int * P_INT;

const p_int ptr1;

const P_INT ptr2;

由于ptr1是简单的宏替换,所以ptr1一个指向常量的指针,其值不可以改变;

ptr2则是一个常量指针,其指向的对象不可以发生变化。

C++11 新标准auto(P61),自动推断变量类型,如auto n=10;

C++11新标准decltype(P62),根据表达式推断变量类型,如decltype(f()) sum=x;decltype(a) b=0;

string str1=”nihaoa”  //拷贝初始化P(76)

stirng str2(“nihaoa”); //直接初始化

string连续加法:

对于string的连续加法而言,+两侧必须有一个为string对象,如

string str=”rrr”+”oooo”;//编译不会通过,因为”rrr”和”oooo”为char *类型,string加法用的是+运算符的重载。应该注意的是”rrr”是C语言中的char *写法,而string则是C++中标准库中的一个类型。

数组在函数参数传递的时候退化为指针,在取地址符(&)、sizeof和typeid等运算符的运算对象时,转换则不会发生。P143

C++显示转换,强制转换分为四种:

static_cast,使用static_cast转换的转换结果和原始的地址相等,因此必须确保转换后所得的结果的正确性,如将int *转换为double*则会产生未定义的结果。

void *p=&d;

Int *a=static_cast<int *>p;

dynamic_cast,

const_cast,将常量对象转换为非常量对象。如:

const int a=10;

int &b=const_cast<int&>(a);

b=99;

reinterpret_cast

在C++中,char* 等于const char *,如char *str=”yyy”;存储在常量区,所以不能通过str来改变str。char str[]=”yyy”;存储在栈中。

C++ 内存分布:

常量区,如char *str=”2222”;

静态区,存储static和全局变量

代码区

函数指针:在代码区中,函数名代表函数的首地址。如有函数int Add(int a,int b);

typedef int (*ptr_func)(int ,int) //声明一个函数指针

ptr_func Add_func=Add;//令Add_func指向Add

int a=Add_func(1,2); //调用Add

static作用:

1、  规定作用域,使用范围只能在本文本内

2、  规定存储方式,存储在静态区

const作用:变量不能修改

C++默认初始化:

1、  任何定义在函数体外的变量调用默认构造函数,内置类型和组合类型没有构造函数,那么则初始化为0;

2、  定义在函数体内的对象默认初始化为未定义的;

3、  对于类类型内的变量,与1相同,如果变量存在默认构造函数,则调用默认构造函数,否则被默认为0

值得注意的是,对于new的动态分配的对象是默认初始化的,这意味着内置对象或者组合类型的对象的值是未定义的,而类类型的对象将用默认构造函数进行初始化。

Int *i=new int;//i是未定义的

int * i=new int();//i=0

string *str=new string(9,’c’);

类的静态成员:

java在第一次使用类的时候会初始化类的静态成员,这发生在类的初始化之前。但是在C++中,并不是在类第一次使用前初始化这些静态成员的。一般而言,我们不能在类的内部初始化静态成员。相反地,必须在类的外部定义和初始化每个静态成员。和其他对象一样,一个静态数据成员只能定义一次,和全局变量一样,静态数据成员定义在任何函数之外,因为一旦它被定义,就将一直存在于整个程序的生命周期里。如:P270

class Base

{

public:

static int age;//不能这样定义:staticint age=19;不能在类的内部初始类的静态变量

}

在main函数中定义:int Base::age=19;//错误,不能在任何函数内部初始化定义类的静态变量。

静态变量的调用:Base base;base.age;或者是Base::age

局部静态变量只会被定义一次,当再次进入函数区域时候,此时局部静态变量已经被定义过了,所以可以直接使用这个局部静态变量:如:

void getstatic()

{

Static int aa=100;//第二次进入函数时不会执行这条语句,会直接执行aa++

aa++;

}

Class和struct的唯一区别是默认的访问权限不同,class默认是private,struct默认则是public

explicit关键字:用于阻止类类型之间的隐形转换,如:

class Person

{

public:

 explicit Person(const char *);

}

那么Person p=”mcfer”;则会报错,如果没有explicit则可以这样写。

Class Person

{

Public:

 Person(string name);

}

那么Person p=”mcfer”;则会报错,因为这里发生了两次隐式转换,一次是const char*转为string,然后将string转为Person。

我们比较常用的是string(const char*)不是explicit;vector<T>(int length)则是explicit的。

与其他顺序容器不同的是,swap两个array会真正交换他们的元素,因此,交换两个array所需的时间与array中元素的数目成正比,这导致array在进行swap交换操作之后,指针、引用、迭代器都不会失效,但是元素值已经与另外一个array中对应的元素值进行了交换。对于其他顺序容器而言,指向容器的迭代器、引用和指针都不会失效。P303

注意,在移动操作及拷贝移动和赋值移动都不是默认合成的。

一般而言,析构函数被定义成virtual的,不然的话没法释放子类部分的内存。

问题:当两个string对象同时指向了一个constchar*,那么调用string的析构函数会发生什么呢?因为string有移动复制构造函数,那么:

String str1=”aaa”;

String str2=std::move(str1);

那么当str1析构后,str2还能够使用,其内部是怎么实现的呢?

Struct和class的唯一区别是默认的成员访问权限和派生类访问说明权限,其中struct为pubic,class为private。

抽象基类:在类的方法中存在=0这样的声明,这说明类是抽象类,不能被实例化。如:

Class Person

{

Void Getage() const & =0//这里用了三种限定符,即const、&和=0

}

派生类覆盖重载函数:

成员函数无论是否是虚函数都能被重载。派生类可以覆盖重载函数的0个或者是多个版本,但是如果派生类希望所有的重载版本都是可见的,那么派生类就需要覆盖父类的所有版本或者是0个版本。如:P551

Class Base

{

Public:

Void sayhello();

}

Class Child1:public Base

{

Public:

Void sayhello(std::string str);

}

此时调用Child.sayhello()会报错,因为找不到sayhello()的版本,子类如果想继承父类的重载版本,那么子类要么重载所有的重载函数,要么一个都不重载,即子类中不重载sayhello(string)版本的话,父类中的sayhello()是可以用的。如果不想重载的话,可以在子类中写using Base::syahello;

当派生类中定义了拷贝构造函数、移动构造函数和赋值操作时,该操作需要负责拷贝、移动和赋值包含基类部分成员在内的整个对象。和构造函数一样,如果我们想拷贝、移动和赋值派生类的基类部分,需要在派生类的这些操作中显式的调用基类的这些函数。否则拷贝函数和移动函数会调用父类的默认构造函数进行基类构造,赋值函数则不会对父类进行赋值,其值是未定义的。

class Base

{

public:

Base(string str);//构造函数

Base(Base&);//赋值构造函数

Base(Base &&);//移动构造函数

Base & operator=(constBase&);//赋值函数

Base& operator(Base&&);//移动赋值函数

virtual ~Base();//析构函数

}

class Child

{

public:

Child(string str):Base(str){};

Child(Child& child):Base(child){};

Child(Child&&child):Base(std::move(child)){};

Child& operator=(Child &child){Base::operator=(child);};

Child&operator=(Child&& child){Base::operator=(std::move(child))};

~Child(){}//自动调用Base的析构函数

 }

构造函数(没有默认构造函数):

delete和析构函数不是一回事。delete在调用析构函数后会释放掉new创建的内存空间,而如果仅仅是调用析构函数的话,此时就是一个普通的函数,要看析构函数怎么写了。如:

class Mystring

{

public:

int age;

char *c;

Mystring()

{

c=new char[10];

strcpy(c,”aaaa”);

}

~Mystring()

{

delete []c;

}

}

Mystring *ms=new Mystring();

ms->~Mystring();//调用析构函数,此时只会执行析构函数里的内容,但是Mystring的内存还是没有释放,这里包含有存在堆中的一个指针和一个int

delete ms//这里会调用析构函数,同时释放掉ms的空间,ms变成未定义的。

new、operator new、placementnew区别:

new opeartor和delete operator就是new和delete操作符,而operatornew和operator delete是函数。

new operator://用于分配内存空间和调用构造函数

(1)    调用operator new函数进行内存的分配,并调用相关对象的构造函数;

(2)    不可以被重载

operator new://用于分配内存空间operatornew(size_t)

(1)    只分配所需要的空间,不调用相关对象的构造函数,当无法满足所需分配的空间时,如果有new_handler,则调用new_handler。

(2)    可以被重载

(3)    重载时,返回类型必须是void *

(4)    重载时,第一个参数必须是要分配空间的大小,类型为size_t

(5)    重载时,可以带其他参数

Placement new://用于调用构造函数

(1)    用法,如:

Person *p=(Person*)(operator new(sizeof(Person)));

new(p)Person(10,”mcfer”);//在p的位置上调用Person的构造函数构造对象。

也可以这样用:

Person *p=new Person(20,”mcfer”);

new(p)Person(99,”uuuu”);//没有调用person的析构函数

new 、operator new 和 placementnew 区别

(1)new :不能被重载,其行为总是一致的。它先调用operator new分配内存,然后调用构造函数初始化那段内存。

new 操作符的执行过程:

1. 调用operator new分配内存 ;

2. 调用构造函数生成类对象;

3. 返回相应指针。

(2)operator new:要实现不同的内存分配行为,应该重载operator new,而不是new。

operator new就像operator + 一样,是可以重载的。如果类中没有重载operator new,那么调用的就是全局的::operator new来完成堆的分配。同理,operator new[]、operator delete、operator delete[]也是可以重载的。

(3)placement new:只是operator new重载的一个版本。它并不分配内存,只是返回指向已经分配好的某段内存的一个指针。因此不能删除它,但需要调用对象的析构函数。

如果你想在已经分配的内存中创建一个对象,使用new时行不通的。也就是说placement new允许你在一个已经分配好的内存中(栈或者堆中)构造一个新的对象。原型中void* p实际上就是指向一个已经分配好的内存缓冲区的的首地址。

 

C++编译与连接:

在c++中,每个cpp文件都是单独编译的。在编译的过程中,每个编译之后的文件都会导出一个导出符号表、未解决符号表和地址重定向表,其中导出符号表里包含这个目标文件导出的一些变量和函数,未解决符号表表示这个目标文件里没有找这些符号的定义,可能有声明。需要链接其他的目标文件查找符号。地址重定向表是符号的重定向信息。链接分为外链接和内链接,外链接表示其他的目标文件可以使用这个目标文件里的外链接变量或者函数。如果在文件中声明了static或者const,则表示这个变量或者函数是属于内链接,不通过导出符号表导出。值得注意的是并没有extern static这种写法,但是存在着extern const,这表示const是外;连接变量。

在头文件中定义了const符号?如果这个头文件被多个cpp包含,则表示这个const符号有多个定义,但是程序并不会报错,编译之后导出的符号表中的符号并没有冲突。但是这导致的后果就是在每个cpp中取得这个符号的地址不一致,如果这个符号是mutable,那么修改这个变量则不会导致其他的目标文件中的值发生变化。

为什么class中不能给static或者const直接赋值。因为static和const表示这个符号是内链接,如果这个class.h被多个文件包含的话,那么修改则会无效。

 

C++中的四种转换:

(1). static_cast<>(),c语言中强制转换的替代品。用于基本类型的转换或者是类型之间的转换,但是类型之间的转换必须是有联系的,且这种转换是不安全的。

(2). dynamic_cast<>(),用于基类和派生类之间的转换。只能用于指针和引用转换,其中,必须存在虚函数,否则编译不会通过。若是转换失败,则返回NULL,这种转换是类型安全的。

(3). const_cast<>(),用于将const去除。返回一个非const变量。只能用于指针和引用转换。

(4). reinterpret_cast<>(),通常为操作数的位模式提供较低层的重新解释。比如讲int转换为指针,或者将指针转换为int,一个明显的现象就是转换前后没有数位损失,所以将int转换为double用这种转换就行不通。这种转换仅仅是复制比特位。


 

 

 


 

C++笔记
2015-07-19 13:55:47 llq108 阅读数 468

1、
`#if !defined(OBJC) && !defined(__OBJC_BOOL) && !defined(__objc_INCLUDE_GNU) && !defined(_NO_BOOL_TYPEDEF)
typedef int BOOL;

endif

define BOOL WINBOOL

typedef BOOL *PBOOL;
typedef BOOL *LPBOOL;

pragma pop_macro(“BOOL”)

endif /* DEF_WINBOOL */

typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned __LONG32 DWORD;
typedef float FLOAT;
typedef FLOAT *PFLOAT;
typedef BYTE *PBYTE;
typedef BYTE *LPBYTE;
typedef int *PINT;
typedef int *LPINT;
typedef WORD *PWORD;
typedef WORD *LPWORD;
typedef __LONG32 *LPLONG;
typedef DWORD *PDWORD;
typedef DWORD *LPDWORD;
typedef void *LPVOID;

ifndef _LPCVOID_DEFINED

define _LPCVOID_DEFINED

typedef CONST void *LPCVOID;

endif

typedef int INT;
typedef unsigned int UINT;
typedef unsigned int *PUINT;

ifndef NT_INCLUDED

include

endif

typedef UINT_PTR WPARAM;
typedef LONG_PTR LPARAM;
typedef LONG_PTR LRESULT;`

c++笔记
2011-11-29 19:03:11 maochencw 阅读数 235

最近在学习c++,今天在看书时,敲了几行代码:

#include <iostream>
void swap(const int &a, int &b);


int main()
{
using namespace std;
int x = 1, y =2;
swap(x, y);


cout << "x = " << x << endl;
cout << "y = " << y << endl;


return 0;
}


void swap(const int &a, int &b)
{
int temp;
temp = a;
//a = b;
b = temp;
//return 0;
}


运行结果是:x = 2

y = 1

很纳闷,为什么会这样,明明已经对x用const进行了限定,怎么还是变化了,看了半天也没发现什么,吃过晚饭,和女朋友一起讨论这件事,在她电脑上把这个程序敲了上去,两个人一起看了会,还是没有什么发现,我突然灵机一动,把swap全部改成大写的Swap,运行后,x = 1 y = 1。原来这个swap是库函数,但是怎么样用自己的swap了,把using namespace std去掉,直接用std::cout, std::endl就行了。

c++笔记

阅读数 4

c++笔记0987新的改变我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编在这里插入图片描述我们增加了如下几点新功能,帮助你用它写博客:全新的界面设计,将会带来全新的写作体验;在创作中心设置你喜爱的代码高亮样式,Markdown将代码片显示选择的高亮样式进行展示;增加了图片拖拽功能,你可以将本地的图片直接拖拽到编辑区域直接展示;全新的...

博文 来自: weixin_43371844

C++笔记

阅读数 193

inlinetypefunc();  //内联函数,编译的时候直接将代码替换到函数使用的位置,减少函数调用class派生类名:继承方式1 基类名1,继承方式2 基类名2,...{    成员声明;}公有继承:基类的public和protected成员的访问属性在派生类中保持不变,但基类的private成员不可

博文 来自: qq_17242837

C++笔记

阅读数 184

C++教程(&C中文网)

博文 来自: qq_24735165

c++笔记

阅读数 77

引用不能为空  无法定义一个什么都不引用的引用    -int&r=NULL; //error  但"野引用"或者"悬空引用"确实是存在的  -int&r=*newint(1);    ++r;    cout    delete&r;    ++r;  //未定义  解引用一个野引用,就跟解引用一个野指针一样,其结果将是未定义的,可能

博文 来自: qq_33319087

c++ 笔记

阅读数 92

重载:   1.在同一作用域中,函数名相同,参数表不同的函数,构成重载关系-voidfoo(void); voidfoo(intn); voidfoo(int*p); charconst*foo(intn,doubled); charconst*foo(doubled,intn);   2.重载与返回类型无关与参数名也无关,相同类型的引用与非引

博文 来自: qq_33319087
没有更多推荐了,返回首页