1.将复杂的公式转化为可递推的简单表达式,通过循环的方法求解或者趋近
2.continue跳转到循环开头,跳过循环块中后面代码
3.break控制无限循环,break退出当前循环,return退出当前函数模块1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18while(true)
{
DoSomethingRepeatedly;
if(expression)
{
break;
}
}
do
{
DoSomething;
if()
break;
}while(true)
for(;;)
{
if()break;
}
4.while循环,0表示false,其他都为true,-1也是true
5.函数重载,处理不同情况的参数的函数
6.变量声明后,养成初始化的习惯,贴别是指针,要初始化为NULL。指针如果未初始化,否则它包含的是随机值,可能导致程序访问非法单元,使程序崩溃。
7.动态分配内存1
2
3
4
5Type* Pointer = new Type;
delete Pointer;
Type* Pointer= new Type[NumElemets];
delete[] Pointer;
对于使用new[…]分配的内存块,需要使用delete[]来释放;对于new为单个元素分配的内存,需要delete来释放。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 #include<iostream>
#include<string>
using namespace std;
int main()
{
cout<<"Enter your name:";
string Name;
cin>>Name;
//Add 1 to reserve space for a terminating null
int CharsToAllocate = Name.length()+1;
char* CopyOfName = new char[CharsToAllocate];
strcopy(CopyOfName,Name.c_str());
cout<<"Dynamically allocated buffer contains: "<<CopyOfName<<endl;
delete[] CopyOfName;
return 0;
}
8.指针的递增递减运算(++ —)
Type pType = Address;
++pType后,将指向Address + sizeof(Type)
cout<<(++pType)
9.const用于指针
指针指向数据为常量,不能修改,但可以修改指针包含的地址,即指针可以指向其他地方
int HoursInDay = 24;
const int pInteger = &HoursInDay;//NO! cannot use pInteger to change HoursInDay
int MonthsInYear = 12;
pInteger = & MonthsInYear;//OK!
pInteger = 13;//NO! Compile fails:Cannot change data
int pAnotherPointerToInt = pInteger;//NO! Compile fails:Cannot assign const to non-const
指针包含的地址是常量,不能修改,但可修改指针指向数据
int DaysInMonth = 30;
int const pDaysInMonth = &DaysInMonth;
pDaysInMonth = 31; //OK! Value can be changed
int DaysInLunarMonth = 28;
pDaysInMonth = &DaysInLunarMonth;//NO! complie fails: cannot change address!
指针包含的地址以及它指向的值都是常量,不能修改(最严格)指向const数据的const的指针
int HoursInDay = 24;
const int const pHoursInDay = &HoursInDay;
*pHoursInDay = 25;//compile fails:cannot change pointed value
int DaysInMonth = 30;
pHoursInDay = &DaysInMonth;//NO!
10.数组和指针有相似之处,解除引用运算符(*)可用于数组,同样数组运算符[]可用于指针
11.使用指针常犯的编程错误
内存泄露(c++运行时间越长,占用内存越多,系统的越慢,如果在new后不再需要,没有配套的delete释放,会出现这种问题。C++没有C#和java的自动垃圾收集器)
int pNumbers = new int[5];//initial allocation
//use pointer pNumbers
…
//forget to release using delete[] pNumbers;
…
//make another allocation and overwrite the pointer
pNumbers = new int[0];//leaks the previously allocated memory 泄露
指针指向无效内存单元(常见导师应用程序崩溃的原因就是无效指针)当指针有效时再释放delete,初始化指针
悬浮指针/迷途指针,务必在初始化时和释放指针后将其设置为NULL,并在使用运算符对指针解除引用前检查它是否有效
12.new发出的分配请求是否得到满足(请求分配的内存量特大或系统处于临界状态,一般不要假定内存分配能够成功,C++提供了两种确保指针有效的方法,默认方法是使用异常,引发std::bad_alloc异常)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27#include<iostream>
using namespace std;
int main()
{
try{
//Request lots of memory space
int* pAge = new int[536870911];
//use the allocated memory
delete[] pAge;
}
catch(bad_alloc)
{
cout<<"Memory allocation failed.Ending program"<<endl;
}
return 0;
}
有一个new变种——new(nothrow),它不引发异常,而返回NULL,让您能够在使用指针前检查其有效性1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#include<iostream>
using namespace std;
int main()
{
//Request lots of memory space,use nothrow version
int* pAge = new(nothrow) int[0x1fffffff];
if(pAge) //check pAge!=NULL
{
//use the allocated memory
delete[] pAge;
}
else
cout<<"Memory allocation failed.Ending program"<<endl;
return 0;
}
13.当参数占用大量内存,在传参的过程中,赋值给函数参数,开销大,引用很用用处(int& Parameter=argument)
函数声明:ReturnType DoSomething(Type& Parameter);
调用:ReturnType Result =DoSomething(argument);
void Calulate(const int& Number,int& Result)//两个引用,第一个接收输入,第二个输出结果
{
Result = NumberNumber; //const限制Number不用修改,所以不可以Number=Number;
//最好将Const用于引用参数,除非函数需要将结果存储在参数中
}
14.避免如下使用
int pNumber = new int;
int pCopy = pNumber;为释放内存,是否需要对它们都调用delete?
这样是错误的,对new返回的地址,只能调用delete一次,另外,最好避免让两个指针指向相同的地址,因为对其一调用delete将导致另一个无效,另外编写程序时,应避免使用有效性不确定的指针。
使用引用,比使用指针好,因为引用不像指针会失效。
15.类声明类似于函数声明,将类本身及其属性告诉编译器。类本身不能改变程序的行为,必须使用它,就像调用函数一样。
16.带默认值的构造函数
class Human
{
private:
string Name;
int Age;
public:
Human(string InputName,int InputAge = 25);
Human(string InputName,int InputAge):Name(InputName),Age(InputAge);
…
}
int main()
{
Human FirstMan; //如果第二个构造注释掉,就会报错,因为当提供了构造函数后,系统不再提供默认构造,但可以Human FirstMan(“EVe”)
Human FirstWoman(“EVe”,18);
}
17.析构函数是重置变量以及释放动态分配的内存和其他资源的理想场所。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36class MyString
{
public:
MyString(const char* InitalInput)
{
if(InitalInput != NULL)
{
...
}
else
Buffer = NULL;
}
~MyString()
{
cout<<"Invoking destructor,clearing up"<<endl;
if(Buffer!=NULL)
delete [] Buffer;
}
}
18. 复制构造函数,确保深复制,编写类的程序必须提供
语法:
class MyString
{
MyString(const MyString& CopySource);
}
MyString::MyString(const MyString& CopySource)
{
//implementation code
}函数的名称必须和类名称一致,它的唯一的一个参数是本类型的一个引用变量,该参数是const类型,不可变的。例如:类X的拷贝构造函数的形式为X(X& x)。
以引用方式传入的当前类的对象作为参数。这个参数是源对象的别名,您使用的它来编写自定义的复制代码,确保对所有缓冲区进行深复制。
没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝。自定义拷贝构造函数是一种良好的编程风格,它可以阻止编译器形成默认的拷贝构造函数,提高源码效率。深拷贝和浅拷贝的定义可以简单理解成:如果一个类拥有资源(堆,或者是其它系统资源),当这个类的对象发生复制过程的时候,这个过程就可以叫做深拷贝,反之对象存在资源,但复制过程并未复制资源的情况视为浅拷贝。
类包含原始指针成员(char 等),务必编写复制构造函数和复制赋值运算符。
编写复制构造函数时,务必将接受源对象的参数声明为const引用
务必将类成员声明为std::string和智能指针类(而不是原始指针),因为它们实现了复制构造函数,可减少工作量
要禁止类对象被复制,可声明一个私有的复制构造函数,确保调用不能通过编译。为禁止赋值,可声明一个私有的赋值运算符。但无法禁止通过实例化多个对象来创建多名总统。
class President
{
private:
President(const President&);
President& operater= (const President&);
…
}
如何只能有一个实例的单例类?
使用单例的概念,它使用私有构造函数、私有赋值运算符和静态实例成员。关键字static用于类的数据成员时,该数据成员将在所有的实例之间共享。将static用于函数中声明的局部变量时,该变量的值将在两次调用之间保持不变。将static用于成员函数(方法)时,该方法将在所有成员之间共享。
总是应该编写一个复制构造函数吗?
如果类的数据成员是设计良好的智能指针、字符串类或STL容器(如std::vector),则编译器生成的默认复制构造函数将调用成员的复制构造函数。然而,如果类包含原始指针成员(如使用int* 而不是std::vector
19.禁止在栈中实例化的类
栈空间十分有限,如果要编写一个数据库类,其内部结构包含数据很大,应该禁止在栈上实例化它,而只允许在堆中创建其实例。关键在于将析构函数声明为私有。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25#include<iostream>
using namespace std;
class MonsterDB
{
private:
~MonsterDB(){};
public:
static void DestoryInstance(MonsterDB* pInstance)
{
//static member can access private destructor
delete pInstance;
}//如果没有将导致内存泄露
...
};
int main()
{
MonsterDB* pMyDatabase=new MonsterDB();
//pMyDatabse->member methods(...)
//delete pMyDatabase;//private destructor cannnot be invoked
//use static member to deallocate
MonsterDB::DestroyInstance(pMyDatabase);
}
20.友元函数与友元类
C++中以关键字friend声明友元关系。友元可以访问与其有friend关系的类中的私有成员。友元包括友元函数和友元类。
21.在静态方法中使用实例变量,应显式地声明一个形参,让调用者将实参设置为this指针。
22.struct来自C语言,默认公有成员,公有继承基结构。与类相似,可放函数等。
struct Human
{
// constructor,public by default
Human(const MyString& InputName,int InputAge,bool InputGender):Name(InputName),Age(InputAge),Gender(InputGender){}
int GetAge()
{
return Age;
}
private:
int Age;
bool Gender;
MyString Name;
};
结构实例化跟类也很像
Human FirstMan(“Adam”,25,true); // is an instance of struct Human
23.friend关键字,友元函数,友元类
24.访问成员,可使用句点运算符(.),也可使用指针运算符(->)。哪种方式更好?
如果有一个指针对象的指针,则使用指针运算符合适;如果栈中实例化了一个对象,并将其存储到了一个局部变量中,则使用句点运算符最合适。
25.基类初始化——向基类传递参数
class Base
{
public:
Base(int SomeNumber)
{
//Do something with SomeNumber
}
};
class Derived:public Base
{
public:
Derived():Base(25) //instantiate class Base with argument 25
{
//derived class constructor code
}
};
如何调用基类中被覆盖的方法、在派生类中调用基类的方法?
需要使用作用域解析运算符(::),myDinner.Fish::Swim();
防止在派生类中隐藏基类的方法?1.使用作用域解析运算符(myDinner.Fish::Swim())2.在派生类中,使用using解除对方法的隐藏3.覆盖所有基类的重载版本
关于继承要注意的!!!
不要仅为重用微不足道的方法而创建继承层次结构。
在派生类中,不要编写与基类方法同名但参数不同的方法,以免隐藏基类方法
26.将子对象复制给Base对象,如显示复制和通过传递参数。会产生切除问题。编译器只将复制Base部分,不是整个对象的复制,而是Base能够容纳的部分。要避免这种无意间的裁剪,不要按值传递参数,应该以指向基类的指针或const引用的方式传递。
27.将析构函数声明为虚函数,可避免delete用于base指针时,不会调用派生类的析构函数的情况发生。
28.不能被实例化的基类被称为抽象基类,从它派生出其他类,要创建抽象基类,可声明纯虚函数
声明方式:
class AbstractBase
{
public:
virtual void Dosomething()=0; //pure virtual method
};
告诉编译器,派生类必须实现方法DoSomething();
class Derived:public AbstractBase
{
public:
void DoSomething()
{
cout<<”Implemented virtual function”<<endl;
}
};
这种基类非常适合用于定义派生类必须实现的接口。
29.使用虚拟继承,解决菱形问题
在继承层次结构中,继承多个从同一个类派生而来的基类时,如果这些基类没有采用虚拟继承,将导致二义性,这种二义性被称为菱形问题
class Derived1:public virtual Base
{
}
30.虚函数应用
对于将被派生类覆盖的基类方法,务必将其声明为虚函数
纯虚函数导致类变成抽象基类,且在派生类中必须提供纯虚函数的实现
务必考虑使用虚继承
别忘了给基类提供一个虚析构函数
别忘了,编译器不允许您创建抽象基类的实例
别忘了,在菱形继承层次结构中,虚继承旨在确保只有一个基类实例。
用于创建继承层次结构和声明基类函数时,关键字virtual的作用不同
31.与函数一样,运算符可以重载(operator override)
运算符声明和函数极其类似:return_type operator_symbol (…parameter list…)
重载运算符的目的就是直观,更方便
单目运算符(只对一个操作数进行操作),实现全局函数或静态成员函数:++ — ! & ~ + - ->(成员选择) 转换运算符
Date& operator ++ ()
{
++Day;
return this;
}
Date operator ++(int)
{
Date Copy(Day,Month,Year); //复制当前对象,在执行递增或递减运算
++Day;
return Copy;
}
32.不能重载的运算符
运算符
名称
运算符
名称
.
成员选择
?:
条件三目运算符
.*
指针成员选择
sizeof
获取对象/类类型的大小
::
作用域解析
重载:
务必实现让类易于运用的运算符,但不要实现无助于实现这个目的的运算符。
对于包含原始指针成员的类,除给它提供赋值构造函数和析构函数外,务必给它提供复制赋值运算符。
如果没有提供复制构造函数和复制赋值运算符,编译器将提供其默认版本,而这些版本不一定会对包含的原始指针进行深复制。
如果使用遵循C++11的编译器,则对于管理动态分配资源(如数组)的类,务必给它提供移动赋值运算符和移动构造函数。
即便没有提供移动赋值运算符和移动构造函数,编译器也不会替您创建它们,而是转而使用复制运算符和复制构造函数。
智能指针类的编写,需要实现运算符*和->的override,还要实现析构函数,并对复制赋值和复制构造的情形深思熟虑。
33.C++类型转换运算符
除dynamic_cast类型转换外,都是可以避免的,仅当需要满足遗留应用程序的需求时,才需要使用其他类型转换运算符。
向下转换(base->derived),除非使用dynamic_cast,否则都不安全!使用dynamic_cast时,记得对转换得到的指针进行检查,看其是否有效!
创建继承层次结构时,应尽量将函数声明为虚函数。在通过基类指针调用这些函数时,如果该指针指向的是派生类对象,将调用相应类的函数版本。
static_cast
用于在相关类型的指针之间进行转换,还可以显式地执行标准数据类型的类型转换(原本可自动隐式转换)
使用static_cast可将指针向上转换为基类型,也可向下转换为派生类型。
Derived objDerived;
Base pBase=&objDerived; //upcast ok!
Derived pDerived=pBase; //Error
Derived pDerived=static_cast<Derived>(pBase); // pass!
只验证指针类型是否相关,而不会执行任何运行阶段检查,可通过编译,但在运行阶段可能导致意外结果。
除了用于向上和向下转换外,static_cast还可在很多情况下将隐式类型转换为显式类型,来引起代码阅读注意作用。
int Num = static_cast
dynamic_cast
与静态类型转换相反,动态类型转换在运行阶段(即应用程序运行时)执行类型转换。
Derived objDerived;
Derived pDerived=dynamic_cast<Derived>(pBase);
if (pDerived)
pDerived->CallDerivedClassFunction();
reinterpret_cast(避免使用)
语法强制重新解释类型,将一种类型转换为另一种,不管它们是否相关。
Base pBase = new Base();
CUnrelated pUnrelated = reinterpret_cast
接收static_cast不允许的类型转换。通常用于低级程序(如驱动程序),在这种程序中,需要将数据转换为API能够接受的简单类型(例如有些API只能使用字节流unsigned char*)
const_cast
起到关闭对象的访问修饰符const的作用
可能该引用const,但是用的第三方库中没有用const,且第三方库无法修改,在这种情况下,const_cast应用。
void DisplayAllData(const SomeClass& mData)
{
mData.DisplayMembers(); //Compile failure:call to a non-const member using a const reference
SomeClass& refData = const_cast
refData.DisplayMembers(); //Allowed!
}
除非万不得已,不用其调用非const函数,可能导致不可预料的行为。
34.避免宏多次包含
复杂设计,头文件间彼此包含会导致递归问题,为了避免这种问题,可结合使用宏以及预处理器编译指令#ifndef和#endif
包含1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef HEADER1_H_ //让预处理器仅在标识符未定义时才继续
#define HEADER1_H_
#include<header2.h>
...
#endif //end of header1.h
header2.h与此类似,但宏定义不同,且包含的是<header1.h>:
#ifndef HEADER2_H_
#define HEADER2_H_
#include<header1.h>
...
#endif //end of header2.h
编写宏函数 宏函数不考虑数据类型,因此使用宏函数很危险,因为不支持任何形式的类型安全
#define SQUARE(x) ((x)*(x))
#define MIN(a,b) (((a)<(b))?(a):(b))
但也是其优点,可以通用数据类型,不用编写两套常规函数。
35.assert宏验证表达式
立即单步执行测试不现实
需要包含
include
int main()
{
char* sayHello = new char[25];
assert(sayHello != NULL); //throws up a message if pointer is NULL
….
delete[] sayHello;
return 0;
}
注意:在发布模式release下被禁用,仅在debug下显示错误消息。在有些开发环境中,assert()被实现为函数,而不是宏。
由于assert在release模式下禁用,对于应用程序正确运行至关重要的检查(如dynamic_cast的返回值),为确保在发布模式下运行,应使用if语句。
36.模板函数和模板类
模板函数可以不指定类型(如int MaxValue = GetMax
include
include
using namespace std;
template
void DisplayComparison(const Type& value1,const Type& value2)
{
if(value1>value2)
return value1;
else
return value2;
}
template
void DisplayComparison(const Type& value1,const Type& value2)
{
cout<<GetMax(value1,value2)<<endl;
}
int main()
{
int Int1=-101,Int2=2011;
DisplayComparison(Int1,Int2);
double d1=3.14,d2=3.1416;
DisplayComparison(d1,d2);
string Name1(“Jack”),Name2(“John”);
DisplayComparison(Name1,Name2);
}
37.模板类和静态成员
初始化模板类静态成员
通用的初始化语法为:
template StaticType ClassName::StaticVarName;
例如:template
38.在头文件中,多次包含防范使用#ifndef、#define和#endif,可避免头文件出现多次包含或递归包含错误,有事可以提高编译速度。
39.模板最重要的最强大的应用是在标准模板库(STL)中。STL由一系列模板类和函数组成,它们分别包含泛型实用类和算法。能够实现动态数组、链表以及包含键-值对的容器,而sort等算法可用于这些容器,从而对容器包含的数据进行处理。
40.STL提供了
用于存储信息的容器,用于访问容器存储的信息的迭代器,用于操作容器内容的算法
提供了两种类型容器:顺序容器、关联容器、容器适配器(Container Adapter)的类
顺序容器:(如数组和列表)
std::vector 动态数组,在最后插入
std::deque 与vector类似,但允许在开头插入或删除元素
std::list 与双向链表一样,对象被连接在一起,可在任何位置添加和删除对象
std::forward_list 类似list,是单项链表,只能沿一个方向遍历
vector和数组类似,允许随机访问元素,即可使用下标运算符[]指定元素在vector中的位置(索引),从而直接访问或操作元素。另外,STL vector是动态数组,因此能够根据应用程序在运行阶段的需求自动调整长度。
==========================
关联容器:(如词典,按指定的顺序存储数据)
std::set
std::unordered_set
std::map
std::unordered_map
std::multiset
std::unordered_multiset
std::multimap
std::unordered_multimap
hash*和unordered*容器有更好的元素搜索性能。
STL迭代器—-模板类
输入迭代器:解除引用,对象可能位于集合中。最严格的输入迭代器只能以只读的方式访问对象。
输出迭代器:执行写入操作。最严格的只能执行写入操作。
上述两种基本迭代器进一步分为三类:
前向迭代器:是输入和输出迭代器的细化,允许输入与输出。可以是const的,只能读取它指向的对象;也可以改变对象,即可读写对象。通常用于单向链表。forward_list
双向迭代器:是前向迭代器的细化,可对其执行递减操作,从而向后操作。通常用于双向链表。
随机访问迭代器:对双向迭代器的细化,可将其加减一个偏移量,还可将两个迭代器相减以得到集合中两个元素的相对距离。随机访问迭代器通常用于数组。(细化指具体化或继承)
STL算法
查找、排序和反转都是标准的编程需求,以算法方式提供这些函数。
常用STL算法:
std::find 在集合中查找值
std::find_if 根据用户指定的谓词在集合中查找值
std::reverse 反转集合中元素的排列顺序
std::remove_if 根据用户定义的谓词将元素从集合中删除
std::transform 使用用户定义的变换函数对容器中的元素进行变换
都是std命名空间中的模板函数,必须包含头文件
迭代器如何将容器和STL算法连接?
code实例:
include
include
include
using namespace std;
int main()
{
//A dynamic array of intergers
vector
//insert sample integers into the array
vecIntegerArray.push_back(50);
vecIntegerArray.push_back(200);
vecIntegerArray.push_back(23);
cout<<"The contents of the vector are:"<<endl;
//walk the vector and read values using an iterator
vector <int>::iterator iArrayWalker = vecIntegerArray.begin();
while(iArrayWalker != vecIntegerArray.end())
{
//write the value to the screen
cout<<*iArrayWalker<<endl;
//Increment the iterator to access the next element
++iArrayWalker;
}
//find an element (say 23) in the array using the 'find' algorithm
vector <int>::iterator iElement = find(vecIntegerArray.begin(),vecIntegerArray.end(),23);
//check if value was found
if (iElement != vecIntegerArray.end())
{
//value was found...Determine position in the array:
int position = distance (vecIntegerArray.begin(),iElement);
cout<<"value"<<*iElement;
cout<<"found in the vector at position:"<<position<<endl;
}
return 0;
}
41.模板template 声明和实现要在一起,不能分开
42.逗号表达式,运算符最低 , 先前面表达式,再后面表达式。
例如x=(i=10 , i*5)