C++高级编程
基本类的构成
-
头文件使用include包含到程序中
#include <iostream.h> 表示对于系统标准库的引用#inuclde "selfWrite.h" 表示对于自己写的库的引用- 拓展名可能不是.h甚至不使用拓展名
-
任何一个头文件都要加上的防卫式声明:避免不同头文件重复定义的声明冲突,通常使用头文件名大写并前后加下划线 #ifndef _FILENAME_H
#define _FILENAME_H
···
#endif
-
C++模板类的作用:避免类型不同而函数相同的重复类的定义 template<typename T>
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<double> c1(2.5, 1.5); // 将complex模板类中的T全部替换为double
complex<int> c1(2.5, 1.5);
}
-
inline函数以代码膨胀为代价,消除了调用时候的堆栈开销,从而提高了函数的执行效率。通常简单的函数使用inline,复杂的函数使用inline可能编译器不会认可 -
构造函数
- 创建对象时,会被自动调用
- 构造函数名与类名相同
- 参数可以有默认参数,如果创建对象时未指定则使用该参数
- 可以使用构造初始化
- 不带指针的函数通常不用写析构函数
- 构造函数可以进行重载,即不同的初值形式
- 空构造函数与含默认参数的构造函数不能进行重载,因为难以同时调用
-
构造函数放在private中无法通过通常形式创建对象,因为外界无法调用构造函数,但是可以通过单例模式进行使用 -
正规:类内不改变对象值的函数加const,目的是将类内函数变成只读函数 eg:T fun() const {return value} -
声明对象时,在对象类型前加const,表示对象内的值只读,但是调用对象内的取值函数如果未加const也会报错,因为对象声明只读但对象内的函数却有写的权力 -
参数传递
- 传值:传递参数的整体,开销大
- 引用:所要传递参数的首地址(相当于c的指针,但最好都传引用,除了小于指针的参数,eg:char类型)
- 常量引用:传的引用内容不希望被修改,则函数定义形参时加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一个对象的过程
- 分配内存:分配一片对象大小的内存空间,并使用void*指向
- 转型:将void*转化成对象类型指针
- 构造函数:调用对应的构造函数(谁调用,谁负责)
-
delete一个对象的过程
- 调用析构函数释放动态分配的内存空间
- 释放指针自身所占的内存空间
-
在VC编译器下,malloc()函数会申请16的倍数的大小的内存空间由三部分组成
- 创建对象所需内存大小
- 将内存空间变成最近的16的倍数的pad
- 首尾分别有一个4Byte的cookie进行表示内存空间的大小和占用情况
-
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进行修饰
- 静态数据:所有该类的对象共用一份
- 静态函数:没有this指针指向,只能处理静态数据
-
静态函数的调用
-
使用静态函数实现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<typename T>
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<double> c1(2.5,1.5);// 将模板类内的T全部替换成double
complex<int> c1(2,5);
}
-
标准库内的函数调用
- 直接使用:
using namespace std; - 声明使用:
using std::cout - 单一使用:
使用时,必须std:: -
面向对象的关系
- 复合(Compositioin)
- A拥有B(可以用于改造原有功能类,即Adapter)
- 构造由内而外,外部类首先调用内部类的默认构造函数后再执行自己
- 析构由外而内,外部类的析构函数首先调用执行析构自己,再析构内部类
- 委托(Delegation)
- 类A内有指向类B的指针
- 编译防火墙,指针指向并不用真正实例化
- 不可更改的共享,更改需要单独给一个副本
- 继承(Inheritance)
- 子类继承了父类函数的调用权
- 构造由父及子,子类实例化先调用父类构造函数后调用子类构造函数
- 析构由子及父,析构子类对象先析构子类后析构父类
-
虚函数的类别
- non-virtual函数:不希望子类重写,
int objectA() const - virtual函数:已有默认定义,希望子类重写,
virtual void error(int a); - pure virtual函数:没有默认定义一定要重写,
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 T>
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<class T>
struct identity{
const T& operator()(const T& x)const{
return x;
}
};
template<class Pair>
sturct select1st{
const typename Pair::first_type& operator()(const Pair& x)const{
return x.first;
}
};
-
c++中struct和class的区别
- 可以继承,能实现多态,能包含成员函数
- struct作为数据结构的实现体,默认的数据访问控制是public的
- class作为对象的实现体,它默认的成员变量访问控制是private的
- 默认的继承访问权限。struct是public的,class是private的。
-
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 <class T>
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<class T1, class T2>
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<class U1, class U2>// U1必须是T1的继承类
pair(const pair<U1, U2>& p)
:first(p.first), second(p.second){}
};
pair<Derived1, Derived2>p;
pair<Base1, Base2>p2(p);
-
泛化模板的特化 template<class Key>
struct hash{};
// 模板的特化
template<>
struct hash<char>{
size_t operator(char x)const {return x;}
};
template<>
struct hash<int>{
size_t operator(int x)const {return x;}
};
template<>
struct hash<long>{
size_t operator(long x)const {return x;}
};
// 调用示范:cout << hash<long>()(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)
{
cout << firstArg << endl;
print(args...);
}
sizeof...(args);
-
char通常是一个字节,即8位。bool通常是1位
namespace和标准库
-
全局属性的变量和函数或class等放到一个namespace中,不同的namespce内的变量和函数可以使用相同的名称 -
防止独立开发的两人或多人,在进行程序合并时出现命名冲突 -
父类类型指针可以指向子类的对象Base *ptr = new Derived; -
程序 = 算法 + 数据结构 -
查看C++编译器的版本 #include<iostream>
int main()
{
std::cout<<__cplusplus;
}
-
auto关键字:根据返回类型推导声明的类型
list<string> c;
···
list<string>::iterator ite;
ite = find(c.begin, c.end(), target);
list<string> c;
auto ite = find(c.begin(), c.end(), target);
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;
int &r = x;
r = rx;
sizeof(r) = sizeof(x)
&r = &x
double imag(const double &im){···}
double imag(const double im){···}
double imag(const double im)const{···}
double imag(const double im){···}
-
java中所有的变量来都是reference,常用于参数的传递
面向对象的理解
- 子类对象有父类的成分,这个也体现在实例化的内存中
- 析构和构造的调用顺序
- 构造由内而外,子类的构造函数首先调用基类的默认构造函数,然后再执行自己的
- 析构由外而内,子类的析构函数首先执行自己的,然后才调用基类的构造函数
- 析构函数和构造函数的调用顺序是相反的
- 继承将数据全部继承,再实例化时需要拷贝自己的内存,而继承的函数是其调用权和重写权
- 函数的调用
- 函数的静态绑定:当调用时,先call函数的内存地址,执行完成后return中断地址
- 函数的动态绑定(虚机制):调用函数时候,需要找到对应的存放函数指针的数组位置fun[n],然后在函数指针数组元 素内的地址找到调用函数的内存地址
- 相同内存大小的指针组成的数组可以找到不同的内存大小的元素
- 通过对象来调用函数,那么这个对象的地址就是
this pointer - 子类的对象可以调用父类的函数,子类对象可以转换成
几个重要的关键字
-
const对象不能调用非const函数 ,其他的对象均可调用其他的函数。尽量加const,因为你写的函数可能被其他人使用const对象进行调用 -
写时拷贝Copy-On-Write:在真正需要一个存储空间时才去声明变量(分配内存),这样会得到程序在运行时最小的内存花销 -
同一个成员函数的const版本和非const版本可以重载,当成员函数重载后,const对象只能调用const函数,非const对象只能调用非const函数 -
new和delete底层调用的还是malloc和free函数 -
new和delete的调用
Foo* pf = Foo;
delete pf;
Foo* pf = ::new Foo;
::delete pf;
-
new[] 使用时,需要在对象数组头部多4个字节标识数组大小,即调用构造和析构函数的次数 -
new的重载第一个参数必须是size_t类型 void *operator new(size_t size){
return malloc(size);
}
|