头文件使用include包含到程序中
#include
表示对于系统标准库的引用#inuclde "selfWrite.h"
表示对于自己写的库的引用任何一个头文件都要加上的防卫式声明:避免不同头文件重复定义的声明冲突,通常使用头文件名大写并前后加下划线
#ifndef _FILENAME_H
#define _FILENAME_H
···
#endif
C++模板类的作用:避免类型不同而函数相同的重复类的定义
template
class complex{
public:
complex (T r = 0, T i = 0)// 正规的构造函数初始化
:re(r), im(i)// 构造函数的构造初始化,不写到下面,下面是进行赋值用的
{}
complex& operator += (const complex&);// 只是一个声明,可以在body外定义
T real() const { return re;}
T imag() const { return im;}
private:
T re, im;
friend complex& _doapl(complex*, const complex&);
};
//模板类的使用
int main()
{
complex c1(2.5, 1.5); // 将complex模板类中的T全部替换为double
complex c1(2.5, 1.5);
}
inline函数以代码膨胀为代价,消除了调用时候的堆栈开销,从而提高了函数的执行效率。通常简单的函数使用inline,复杂的函数使用inline可能编译器不会认可
构造函数
构造函数放在private中无法通过通常形式创建对象,因为外界无法调用构造函数,但是可以通过单例模式进行使用
正规:类内不改变对象值的函数加const,目的是将类内函数变成只读函数
eg:T fun() const {return value}
声明对象时,在对象类型前加const,表示对象内的值只读,但是调用对象内的取值函数如果未加const也会报错,因为对象声明只读但对象内的函数却有写的权力
参数传递
C++是面向对象语言中效率最好的,所以要将每个影响习惯的小细节内化为习惯,才能编写高效高质量的C++程序
友元函数:可以直接使用类内封装好的数据,而不需要通过取值函数
友元函数可以直接使用类内封装的数据,而其他函数必须经过类内的取值函数调用,效率低但是不会破坏封装性
相同类的各个对象互为友元,不破坏封装性
class complex{
public:
comoplex(double r = 0, double i = 0)
:re(r), im(i)
{ }
// 同类对象的直接调用
int func(const complex& param){
return param.re + param.im;
}
private:
double re, im;
};
int main()
{
complex c1(2,1);
complex c2;
c2.func(c1);// 不破坏封装性
}
函数运算结果的存放
任何函数都有一个隐含的this指针参数,指向调用该函数的对象
使用引用的好处:传递者无需知道接收者的形式,即return返回对象可以和函数类型不同
通常不要把函数设置成void类型,要设计成目的操作数类型,避免出错
需要使用返回值的函数,不能返回引用,因为函数在调用结束后对释放内部的引用内存区域
类型名称( 操作 )
创建一个临时对象用来存放该操作的结果,不需要声明名称,在下一行就释放内存,速度快。常用于函数值的返回
函数返回值需要在函数中进行创建,则以值的形式返回,避免函数堆栈释放引起的无效引用
函数返回值可以使用参数,则可以使用参数的引用进行返回,速度快
接受带指针参数的类需要三个基本函数:
// 正规:类内只写函数的声明和参数类型,函数的body在类外实现并加inline
class String
{
public:
String(const String& str);// 拷贝构造函数
String& operator=(const String &str);// 拷贝赋值函数
~String();// 析构函数
private:
char * m_data;
};
inline String::String (kconst char* cstr = 0){
if(cstr){// 非空字符串
m_data = new char[strlen(cstr) + 1];
strcpy(m_data, cstr);
}else{ // 空字符串
m_data = new char[1];
*m_data = '\0';
}
}
inline String& String::operator=(const String& str){
// 如果没有自我赋值检测,在销毁自我后会导致复制内容也被销毁而出错
if(this == &str) // 检测是否为自我赋值,是则直接返回自己
return *this;
//销毁,创建,复制
delete [] m_data;
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
return *this;
}
inline String::~String(){
delete[] m_data;
}
动态分配的内存空间在使用完成后要及时释放,如果没有释放则为内存泄漏,即new和delete必须搭配使用
类内指向的动态分配的内容不属于类,进行对象copy时,不会复制一份,而是与原对象指向相同
String s1(s2) 等同于 String s2 = s1
别名是一件危险的事情,尽量每个对象都有自己的完整的copy
当调用函数时,函数本身会形成一个stack用来放置它所接收的参数和返回地址
heap是由操作系统提供的一块全局的内存空间,程序可动态分配从中获得一块内存区域
static在作用域结束后仍然存在,直到整个程序结束
new一个对象的过程
delete一个对象的过程
在VC编译器下,malloc()函数会申请16的倍数的大小的内存空间由三部分组成
new和delete在array下要注意
m_data = new char[strlen(cstr) + 1];
delete[] m_data; // 注意[ ]的使用
对象的创建和销毁
```c++
class Complex{···}
···
{
Complex* p = new Complex;
···
delete p;
}
```
对象的地址实际相当于调用该类对象的this指针,即对象调用类内函数是将对象名地址作为this指针进行传递调用的
complex c1;
cout << c1.real(); // 实际相当于complex::real(&c1);
类内的数据和函数都可以使用static进行修饰
静态函数的调用
使用静态函数实现Singleton单例模式
// 将静态变量函数封装到外面,因为静态变量只有当其函数被调用时才会被初始化
class A{
public:
static A& getInstance();
setup(){···}
private:
A();
A(const A& rhs);
···
};
A& A:: getInstance(){
static A a;
return a;
}
class template 类模板
template
class complex{
public:
complex(T r=0, T i=0)
:re(r), im(i)
{}
complex& operator += (const complex&);
T real() const{return re;}
T imag() const{return im;}
private:
T re,im;
};
// 使用类模板
{
complex c1(2.5,1.5);// 将模板类内的T全部替换成double
complex c1(2,5);
}
标准库内的函数调用
using namespace std;
using std::cout
使用时,必须std::
面向对象的关系
虚函数的类别
int objectA() const
virtual void error(int a);
virtual void draw() const=0;
纯虚函数的子类一定要重写,空函数的不一定
继承的本质
谁调用,谁负责,谁就是类的this指针
委托(Delegation)+继承(Inheritance):可以实现一份数据多种方式显示
类内的static,要通过类名在类外进行初始化
勿在浮沙筑高台
标准库时使用模板编程的思维做出来的、
尽量使用const,因为它可以接受const和非const参数
转换函数
class Fraction // 转换成分数转化为小数的类
{
public: // 首先定义public函数并定格写
// 先写构造函数,注意初始化的赋值格式
Fraction(int num, int den=1)
: m_numerator(num), m_denominator(den){}
// 该对象发生double强制类型转换时候会自动调用
operator double() const{
return (double)(m_numerator / m_denominator);
// 运算符的重载
Fraction operator + (const Fraction& f){
return Fraction(···);
}
}
private: // 后写private
int m_numerator; // 分子
int m_denominator; // 分母
};
int main(){
Fraction f(3, 5);
double d = 4 + f; // 自动调用f的double函数
Fraction d2 = f + 4; // error,方法调用二义性,可在构造函数前加explict
}
无二义性:当编译器发现同时的多条符合的方法调用时,会报错
explict关键字通常用于构造函数前面, 是防止类构造函数的隐式自动转换
智能指针类:将类设计的像指针一样但是比指针更聪明
template
class shared_ptr
{
public:
//c++满足指针行为的两个函数
T& operator*()const{
return *px;
}
T* operator->()const{
return px;
}
shared_ptr(T* p) : px(p){}
private:
T* px;
long* pn;
···
}
->
可以一直使用,直到指向数据
迭代器类:本质是利用双向数据链表的一个额外指向其中一个元素的指针
// 迭代器类的基本函数
T& operator*()const{
return (*node).data;
}
T* operator->()const{
return &(operator*());
}
仿函数类:
// struct或class中出现operator(),则为仿函数
template
struct identity{
const T& operator()(const T& x)const{
return x;
}
};
template
sturct select1st{
const typename Pair::first_type& operator()(const Pair& x)const{
return x.first;
}
};
c++中struct和class的区别
const是限定函数类型为常成员函数,指不能有任何改变其所属对象成员变量值的功能
函数模板的实参推导和函数推导
class stone
{
public:
stone(int w, int h, int we)
:_w(w), _h(h), _weight(we)
{ }
bool operator<(const stone& rhs)const
{return _weight < rhs._weight;}
private:
int _w,_h,_weight;
};
template
inline const T& min(const T& a, const T&){
return b < a ? b : a;// 进行比较时,首先找类内的符号重载
}
stone r1(2,3), r2(3,3), r3;
r3 = min(r1, r2); // 不需要标明类型,编译器可以进行实参推导
成员模板
// 将一个由对象A和对象B构成的pair拷贝进一个由类A和类B构成的pair
class Base1{};
class Derived1:public Base1{};
class Base2{};
class Derived2:public Base2{};
template
struct pair{
tppedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair()
:first(T1()), second(T2()){}
pair(const T1& a, const T2& b)
:first(a), second(b){}
template// U1必须是T1的继承类
pair(const pair& p)
:first(p.first), second(p.second){}
};
pairp;
pairp2(p);
泛化模板的特化
template
struct hash{};
// 模板的特化
template<>
struct hash{
size_t operator(char x)const {return x;}
};
template<>
struct hash{
size_t operator(int x)const {return x;}
};
template<>
struct hash{
size_t operator(long x)const {return x;}
};
// 调用示范:cout << hash()(100);
范围偏特化
template <typename T>
class C
{
···
};
template <typename T>
class C<T*>
{
···
};
// 调用,模板范围的特化
C<string> obj1;
C<string*> obj2;// 调用第二个
模板模板参数
template<typename T,
template<typename T>class Container>
>
class XCls
{
private:
Container<T> c;
public:
···
};
template<typename T>
using Lst = linst<T, allocator<T>>;
XCls<string,Lst> mylst2;
variadic templates可变模板参数C++11
void print(){}// 当一包参数全部打印完成,调用这个函数
template <typename T, typename... Types>
void print(const T& firstArg, const Types&...args)// 分为一个和一包pack
{
cout << firstArg << endl;// 每次调用打印第一个参数
print(args...);// 递归调用,每次将一包参数中剩余的那一部分传入
}
sizeof...(args);// 可以标识一包pack参数的个数
char通常是一个字节,即8位。bool通常是1位
全局属性的变量和函数或class等放到一个namespace中,不同的namespce内的变量和函数可以使用相同的名称
防止独立开发的两人或多人,在进行程序合并时出现命名冲突
父类类型指针可以指向子类的对象Base *ptr = new Derived;
程序 = 算法 + 数据结构
查看C++编译器的版本
#include
int main()
{
std::cout<<__cplusplus;// 双下划线
}
auto关键字:根据返回类型推导声明的类型
// 不用auto
list<string> c;
···
list<string>::iterator ite;
ite = find(c.begin, c.end(), target);
// 使用auto,但是新手尽量不要使用
list<string> c;
auto ite = find(c.begin(), c.end(), target);
// error,编译器无法推导
list<string> c;
auto ite;
ite = find(c.begin(), c.end(), target);
ranged-base for
for(变量 : 容器){// 容器是一个数据结构,编译器将容器内的元素赋值到变量中
statement;
}
语法糖(Syntactic sugar),也译为糖衣语法:
指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。
引用reference和pointer指针
int x = 0;
int rx = 5
int *p = &x;// 指针p指向x
int &r = x;// 引用r代表x,实质也是指针,但是不能改变指向
r = rx;// r仍然代表x,这是用rx给r赋值,通常x也为rx的值
// 引用值的大小和地址与原值相同,但是这是编译器的假象
sizeof(r) = sizeof(x)
&r = &x
// 函数的调用矛盾,两者不能同时存在
double imag(const double &im){···}
double imag(const double im){···}
// 函数后面的const算函数签名的一部分,可以并存
double imag(const double im)const{···}
double imag(const double im){···}
java中所有的变量来都是reference,常用于参数的传递
this pointer
const对象不能调用非const函数
,其他的对象均可调用其他的函数。尽量加const,因为你写的函数可能被其他人使用const对象进行调用
写时拷贝Copy-On-Write:在真正需要一个存储空间时才去声明变量(分配内存),这样会得到程序在运行时最小的内存花销
同一个成员函数的const版本和非const版本可以重载,当成员函数重载后,const对象只能调用const函数,非const对象只能调用非const函数
new和delete底层调用的还是malloc和free函数
new和delete的调用
// 优先调用成员函数的new和delete,若无则调用全局的new和delete
Foo* pf = Foo;
delete pf;
// 直接调用全局的new和delete
Foo* pf = ::new Foo;
::delete pf;
new[]
使用时,需要在对象数组头部多4个字节标识数组大小,即调用构造和析构函数的次数
new的重载第一个参数必须是size_t类型
void *operator new(size_t size){
return malloc(size);
}