c++学习笔记

  • c++学习笔记
    • 学习1
      • 标准库头文件
      • 命名空间
      • 输入输出
      • 命名空间
      • 结构
      • 函数
      • 程序内存分配
    • 学习2
      • 引用
      • 宏函数:
      • 内存管理:
      • 关键字,运算符:
      • 成员指针:
      • 字符串:
      • 数组:
    • 学习3
      • 指针:
      • 面向对象:
    • 学习4
      • 静态成员:
      • 动态创建和释放对象
    • 复习:
    • 学习5
      • 运算符重载:(overloding)
      • 友元函数:
    • 学习6 c++的继承:
      • 对象的继承:
      • 多重继承:
      • 虚函数:
      • 纯虚函数:
      • 多态
      • string类
    • 学习7
      • 异常:
      • I/O 接口:
        • 输入:
    • 推荐参考文献

c++学习笔记

学习1

标准库头文件

调用C语言的标准库头文件,一般去掉后面的.h,在前面加上c,stdio.h==>cstdio

命名空间

标准库中的名字都放在一个叫Std的命名空间中。使用时要加std::前缀,也可以在头文件下用 using name spase std,来表示,其自动在标准库名字前加std::

输入输出

cin>>变量/cout<<数据,可以连成长串。换行用endl。

调试问题:
Native’ has exited with code 0 (0x0).
问题分析:
出现这个提示,其实表示当前程序是完全正确的,它显示了程序在加载调试时的调试信息。那个 has exited with code 0(0x0) 返回这个代码0 表示程序是正常的运行和结束的。
如果出现那个黑框一闪而过,解放方案:不直接点击Debug 或 F5来运行, 而是用Ctrl + F5 来运行

命名空间

C++中所有的类,函数,对象等都在指定的命名空间中,命名空间是一种逻辑分组机制,如果一些声明按照某种准则在逻辑上同属一个集团,就可以将它们放在同一个命名空间,已表明这个事实。
防止一个项目中,不同人写的程序,带来冲突
使用某个命名空间下的对象或函数:
namespace-name::member-name ::与运算符,用来表示某种限定,范围。
定义:
namespace 名字
{
变量声明、定义,函数声明、定义。。。
}
多个同名命名空间会合并在一起。也可以在命名空间里嵌套命名空间。使用:命名空间::内部命名空间::XX
全局命名空间:(匿名命名空间)定义类,全局变量,全局函数等,可以直接用 :: 访问,也可以不加。

结构

定义结构,结构被当做类使用,在使用结构,联合,枚举类型时,不在需要重复:struct,union,enum关键字。结构定义时,不但是数据可以使函数,或类型。
枚举不等同与整数类型,如果需要转换类型可以强制转换。
布尔类型《stdbool.h》c++自带,只有是否两个值,可以自贡转换成整数1/0.非零即真。
函数:void *类型:严格限制,不能赋值给其他地址类型。
字符字面量 :* 在C语言中没有真正的字符字面量,用的实际都是int
强制类型转换在C的基础上增加了一种形式:类型(数据),但是不提倡类型转换。
四种方式:
static_cast<类型>(数据) 用于数值类型之间以及void*和别的类型之间。
reinterpret_cast<类型>(数据) 用于数值类型与地址类型之间或者地址类型相互之间。
const_cast(常量的地址) 去掉对地址所指向的目标的const限制
dynamic_cast

函数

潜规则:1,默认返回int类型,2,孔参数表表示参数个数随意,C++中则表示无参数。因此在调用函数前必须声明或者定义。
重载:允许多个函数同名。但要求不同的参数表(不同类型,个数,顺序)。在同一作用域中用同一函数名定义多个函数,这些函数的参数个数和参数类型不相同,这些函数用来实现不同的功能 。
形参默认值:如果函数的某个形参在调用时绝大多数情况下都传递视为是某个特定实参,那么皆可以把这个实参值指定为这个形参的默认值调用时可以不再传递。有默认值的形参必须靠右。
哑元:多余的形参不需要指定名字,因为,C++读取函数只读取函数参数的类型。

程序内存分配

  1. 栈区(strack):由编译器自动分配释放,存函数的参数值、局部变量值。
  2. 堆区(heap):程序员分配释放,不释放由OS收回。类似链表
  3. 全局区(静态区):static存放全局变量和静态变量,初始化一区域,未初始化在相邻区域。最后程序结束系统释放
  4. 文字常量区:常量字符串,系统释放。

学习2

引用

将引用作为函数参数,指向指针变量的引用,改变引用比仅改变指针变量,也改变指针变量指向的值。这里引用就是指针变量的替身(别名)

宏函数:

基本不用,意识不符合强类型的要求,二有副作用。C++中应用内联函数,inline.内联函数会在调用的地方展开函数代码二不是产生调用,跟宏函数一样高效。却有明确的参数类型没有副作用。函数频繁使用是,可以设置为内联函数,内嵌到主函数中。是否执行inlinede 要求,完全由编译器自己决定。用内联函数时,先求出实参的值,再将实参值赋给形参值。

注:在sublime3中出现乱码,装完相应的pakeage后运行时可以用热键ctrl+shift+c.

内存管理:

C语言malloc//free//calloc//realloc,返回返回的是void*,c++中要明确类型来分配内存。
malloc函数必须指定需要开辟的内存空间的大小。malloc(size).size是字节数,需要事先求出,一般用 sizeof运算符求出,其只能从用户处知道应开辟空间的大小,而不知道数据的类型。因此无法使其返回的指针指向具体的数据,返回值一律为void *类型,必须在程序中进行强制类型转换,才能使其返回的指针指向具体的数据。
new 类型 【初值】
用new来分配数组空间,不能指定初值,指定数组的大小。
delete【】指针变量
new失败会throw报出异常导致程序停止,可以nothrow来请求内存失败是时像C内存管理函数一样返回null,头文件.

关键字,运算符:

and&&/ or||/not!/and_eq&=//not_eq!=/bitand&/bitor|/xor^/compl~/or_eq|=/xor_eq^=.

成员指针:

```
struct Date{
     int year;
     int month;
     int day;
};
int (Date::*) p=&Date::year ;    //括号表示是Date中的内部地址
p=&Date::month;
```

静态局部变量值初始化一次,变化后值一直保留。不要返回局部变量的地址,除非是静态局部变量。

字符串:

string类型,头文件string.同样支持像字符数组那样按下标访问元素,支持+,=,各种比较运算,不用考虑空间问题。成员函数size()``length()都可以去的字符串长度,成员函数c_str()可以返回对应风格的c风格字符串(采制只读形式)。

数组:

vector<类型> 数组名(长度,元素初始值),可以用resize(长度)调整大小,用size()去元素个数。头文件

学习3

指针:

用来保存某种类型变量的地址。
取地址&
取某个地址所指向的变量用:*地址(*地址).成员 地址->成员
如果向函数传递参数时,函数能操作某个变量的值,就把变量的地址传递给函数,函数就可以根据地址找到内存中的那个变量,从而取得或者改变变量的值。
地址运算:加减一个整数,按照地址的数据类型即p+n,p-n,p[i]–> *(p+i).
定义指针时建议带初始化从而避免野指针,不要通过空指针或者目标已经被释放掉的指针去找变量(*),不要返回非静态变量的地址作为函数的返回值。作为函数形参的指针尽量const 修饰。

面向对象:

  • 抽象:每个实体对象度偶有很多数据和行为,我们只提取关心的数据和行为。
  • 封装:用抽象的结果来描述实体的类型,称为封装。在c++中可以用结构来实现封装,用class关键字实现真正的封装,成为类。
    类是对象的抽象,对象是类的特例。封装好的类和结构都只是一个类型。
    定义类

    class XXX
    {
    
    成员变量或成员函数的定义
    }

结构和类的区别
结构默认情况下就是简单地打包,内容对外透明。可以通过结构变量名访问他的任何一个成员。
类外定义成员函数要用作用域运算符::
多文件结构:一个类一般写成一个.h文件和一个.cpp文件,在.h中只对类进行声明,没有函数体,在.cpp文件中包含.h文件并给出程艳函数的定义(包括函数体)。
一般是:

属性.h +行为.cpp+执行主体(main).cpp

一般创建一个对象时会自动调用一个成员函数,成为构造函数constrctor。函数名就是使用类名。无返回类型。可以用来进行初始化。
一创建就调用。
创建对象时如果不传参数,不要加空括号,因为类相当于一个数据类型,如果加空括号,会被误认为是声明
构造函数可以重载,一般访问限制为公开public。。。
如果类中没有构造函数,编译器不会产生一个空函数,有构造函数,编译器 不会产生缺省构造函数。

小范围内重名的话,一般是局部变量优先。
this指针:成员函数自带的,指向本类对象的指针,他的值是调用当前成员函数的当前对象的首地址。调用对象a的成员函数f,实际上是在调用成员函数时自动用this指针指向对象a,从而访问对象a的成员。需要时可以显示的使用this指针,a.show()==a.show(&a)==a.show(a类 *this)
其中 *this 表示被调用成员函数所在对象,this所指向的对象,就是当前对象。

学习4

静态成员:

属于整个类的数据或行为。
静态数据成员
类内声明,类外定义,定义要用类名加作用域运算符指明。对象共享,所有类的对象共享,类可以引用。不定义对象,类也可以引用。定义为私有时,用公用成员函数引用,不可直接引用。他的作用域只限于定义该类的作用域内。
静态成员函数
属于类所有,不依赖与任何对象,没有this指针,所以只能访问静态成员不能访问类中的非静态变量。访问非静态成员应该加对象名和成员运算符.。
初始化列表
构造函数定义时在括号后加冒号,成员数据(初始化值){}多用逗号分隔开。初始化列表用来初始化常量成员和引用成员。

动态创建和释放对象

    new类名                    delete 地址
    new 类名(参数)     delete 地址
    new 类名【个数】      delete【】地址

返回都是地址。free(P)释放的是释放指针P指向的动态内存。delete P;P=NULL;

复习:

面向对象:封装(类和对象),前提是抽象出对象的公共属性和行为。并考虑如何来实现。
类定义:数据,函数,访问限制(public(公共的,允许类内类外的函数可以访问),private(允许类内的成员函数访问))
构造函数:创建每个对象时总会自动调用构造函数。可以重载,但要注意函数的二义性。
this指针:固定指向当前的对象,只存在成员函数中,对那个对象调用成员函数,this就指向那个对象,成员函数传递的是当前对象的地址,有时候可以显示的用this指针, 静态成员函数没有this指针,不能调用类中的非静态数据数据。
一个对象的创建和释放:
全局变量:main执行前穿件返回后结束。
静态局部变量:第一次调用时创建,main返回后释放。
普通局部变量(带形参):。每次执行时创建,超出作用范围后释放。
临时结果:临时创建
动态变量:执行new时创建,执行delete时释放。用new动态的建立了一个对象,在用delete释放该对象前,先调用该对象而析构函数。
析构函数:创建一个对象时自动调用构造函数,对象生命周期结束时,自动调用析构函数。也可以认为的设置何时调用。一个类中可以存在多个构造函数*(重载),但只能有一个析构函数。不返回任何职,没有函数类型,也没有函数参数(参数列表)。用来在撤销对象占用内存之前完成清理工作。如果想在main之前或者面之后做一些工作,可以用全局对象的构造函数和析构函数来实现。一般针对对象占用的额外分配的资源。
默认析构函数:如果一个类没有定义析构函数,编译器会自动产生一个析构函数,什么也不做。
匿名对象:定义匿名的对象,调用之后就被立即释放。编译器对匿名对象的使用,进行最大限度的优化。前提是使用一次,那么提倡使用。
形式:类名 (参数表);
如果要禁止自动调用匿名对象,需要用explicit修饰构造函数.
复制构造函数
指定初始值用类型后面直接用空圆括号零初始化。:类型名()。对于基本类型,是数值0,对于类或者结构类型而言是匿名对象。
同类对象初始化一个新对象时,调用复制构造函数。一个类如果没有定义拷贝构造函数,编译器会自动产生一个,数据逐个复制对应成员。如果其中有指针成员指向开辟的动态内存,则由于两个对象指向一个内存,导致delete内存最后时重复释放。避免要定义复制构造函数,。是对象指向自己的动态内存。要开辟一片新的内存。

学习5

运算符重载:(overloding)

c++中,运算符都被当成函数,允许自己规定运算符如何工作,方法就是自己定义相应的运算符函数。如:operator&;operator<<;称运算符重载。运算符代替运算符,参数代替运算符的操作数,函数返回值对应运算符的运算结果。
普通类外函数访问类中成员函数,只能访问公共的成员函数,不能访问私有的成员函数。
运算符重载函数的形参和返回值尽量用引用,定义为const,
运算符定义格式:全局函数:`返回类型 operator 运算(参数表)“
双目:运算结果类型(返回)operator 运算符 (操作数1,操作数2)
单目:运算结果类型(返回)operator 运算符 (操作数)

友元函数:

这个类授权这个函数可以访问本类的对象中的私有成员数据,友元函数可以是普通函数,也可以是友元成员函数,是哪个类的友元函数,要在那个类中声明,一般在类外定义,引用私有成员时要加上对象名,必须指明要访问的对象。友元类,单向的,关系不能传递。B是A的友元类,类B中所有函数都是A的友元函数。可以访问A的私有成员。
运算符函数定义格式2:成员函数:a.operator+(b)
返回类型 operator 运算符(除了第一个操作数之外的参数列表)
双目:运算结果类型 operator 运算符(第二个操作数)
单目:运算结果类型 operator运算符()

以成员函数形式定义时,第一个操作数作为了当前对象,不需要以实参传递,只需要传递剩余的操作数就可以了。在运算符函数里可以通过*this或者this->来访问第一个操作数。
规定只能用成员函数的[],(),=,->,类型转换。
类型转换运算符函数形式
operator 类型名()
不需要写返回类型,跟类型名是一致的,只能是成员函数。
圆括号函数作为运算符,参数个数不定。
返回类型 operator ()(参数表)
支持圆括号运算符的对象也称函数对象,因为适用形式特别像函数。

学习6 c++的继承:

对象的继承:

定义一个类想用新的类,就可以使用继承。
class Newclass:public Oldclass{};
新类可以继承旧类的全部成员,可以扩展新成员。继承有public/private/protected三种,一般用public方式,旧类称为父类或者基类,新类称子类或者派生类。父类成员全部继承(复制)到子类中成为子类的成员,其中私有的成员在子类中不能直接访问,公开成员可以在子类中直接访问。父类中可以用protected来作为成员访问限制,成为保护成员,这种成员对外跟private一样,但在子类中允许直接访问。***由于protected造成类封装泄露,所以一般不用以免父子间有太强的耦合。

不同继承方式区别
继承后子类的成员对外访问限制。私有继承全部变为私有的;保护继承私有还是私有,其余是保护成员,允许子类直接访问保护类型成员数据,别的类就不允许类,谁继承谁能用。public公共继承,保持原有的访问限制。。
继承主要作用是代码重用,以及为多态提供支持 。

创建对象的顺序
1. 构造函数首先调用父类的构造函数,然后按照顺序创建成员对象,执行构造函数的函数体。析构函数与构造相反。构造函数默认调用父类构造函数时不传参数,如果需要传递参数,需要在初始化列表中用父类类名来表示。*初始化列表各项排名不分先后。
2. 调用子类的成员函数,会自动隐藏来自父类的同名函数。即使参数表不同也不会构成重载。如果确实需要调用来自父类的成员函数,要用”父类名::”指明。

多重继承:

一个类可以继承多个类,把所有父类的成员都全部及继承过来,可以每个类指定一个继承方式。
多个父类的构造函数按照顺序继承,与初始化列表中的顺序无关,
虚继承
ambiguous access of ‘setname’
1> could be the ‘setname’ in base ‘Person’
1> or could be the ‘setname’ in base ‘Person’
希望某一个类被继承到某一级子类时有了多份要保留一份,(上面的错误),这个类在被继承的时候应该被声明为虚继承:用virtual声明,此类称 虚基类
虚继承中虚基类的构造函数有最低级的合并的子类的构造函数直接传递参数。

       虚基类
      /   ^    \
  子类1   |     子类2
    \     |      /
        子类

多态:poiymorphism
对各种对象发出同一种指令,各种对象会根据自身状况做出不同的响应。

虚函数:

如果希望在调用函数时系统根据对象的真实类型去转掉用相应的函数,需要把那个函数声明为virtual虚函数。子类中可以覆盖这个函数也自动成为虚函数。覆盖(override)要求函数名和参数表以及返回类型都要相同。(父类中返回父类指针,子类中可以返回子类指针,这也算相同。)

虚函数表:编译器把这个类的全部虚函数的地址都保存在一张表中(数组)。每个这种类的对象会隐藏一个指针指向虚函数表,(在构造函数中自动生成),对象的长度会增加四个字节用来放指向虚函数表的指针。

string类,有指针指向动态内存,要写三个函数 ,复制构造函数,析构函数,=赋值运算符函数。

利用多态实现类型识别
* dynamic_cast() * 用于父子类之间:1,把父类类型对象转换成子类类型dynamic_cast<子类类型>(父类对象);不成功,抛出异常 。一般用这个结果来初始化一个子类类型对象的引用,或者当场使用2.dynamic_cast<子类*>(父类类型的对象的地址),成功就返回一个正常的子类地址,失败结果为NULL。第二种更好用。
dynamic_cast要求类有虚函数。

  • typeid关键字实现类型识别。
    #include
    返回一个type_info&,typr_info里面有一个成员name()表示类型的名字,type_info支持==和!=比较两个类型是否相同。依赖于编译器。如果类有虚函数,也能利用多态进行类型识别。

纯虚函数:

1. 徒有其名,而无其实。没有必要活不应该有函数体的虚函数,(在实际运行中我们知道这个虚函数不会被执行的虚函数),用“=0;”来取代函数体。=0不是返回值,只是说明他是纯虚函数,声明语句最后有分号。
2. 纯虚函数的类称为抽象类,缺省函数体,不允许用抽象类来创建对象,无法分配内存,总用来做父类。在声明虚函数时,被初始化为0。在派生类中定义,才具备函数功能,才能被调用。子类对象可以当成父类对象来引用,或者用父类指针指向子类对象。  

在应用多态时,必须通过父类指针或者引用来访问子类对象,不能重建一个父类。

多态

也称动态绑定、动态关联,统一接口,便于维护和替换。

通过父类指针 动态释放子类对象时,默认调用父类的析构函数,如果希望使用对象多态调用对象所属的子类的析构函数,应该在父类中把析构函数也声明为虚函数。(当基类的析构函数为虚函数,无论指针指的同一族中的哪个类对象,当对象撤销时,系统会采用 动态关联(多态) ,调用相应的析构函数,对该对象进行清理工作。且又该基函数派生的所有派生类的析构函数也都自动称为虚函数。)
构造函数不能声明为虚函数,这是因为在执行构造函数时类对象还未建立过程,当然谈不上函数与类对象关联。

string类

C++风格字符串类,
构造:string (const sting& s),string(const char* s),string(int n,char c);
运算符:<<,>>,=,+,+=,[]下标,比检查越界,用at(int)检查越界,抛出异常。
比较运算符<,<=,>,>=,==,!=
长度:size(),lenght(),bool empty().resize(newsize,fillc)
转换成C风格:c_str(),data()不保证“\0”,copy(char* to,int 字符数,int start=0)复制从start位置开始的n个字符到to所指向的地方去。
字符串:substr(int start,int n)返回从start开始的n个字符串组成的一个字串string新对象,原对象不变。
追加:append(int n,char c)在末尾追加n 字符
查找:成功返回下标(位置),没找到返回string::nops。

    find(char c,int start=0)
      find(const string& s,int start=0)
        find(const char* s,int start=0)
    rfind(...反方向查找)
    string::nops:可以用来表示末尾,(-1)
    find_first_of(字符串s(如“+-*/”),int start =0),从开始位置找字符串s 中包含的字符的第一个字符。。
    find_last_of(.....)
    find_first_not_of(......)

删除erase(int start=0,int n=string::nops)
替换replace(int start, int n, 新字符串)
replace(int start, int n,int n2,char c2 )把start位置开始的n个字符替换成新字符或者n2个c2
插入insert(int pos,新字符)
insert(int pos,int n,char c)
行输入gets()容易越界,
fgets(buf,sizeof(buf),stdin)保留换行符在末尾,
scanf(“%[^\n]”,buf)易越界
string s;getline(cin,s);

学习7

异常:

    不常发生但无法避免。
    返回错误码
    设置errno全局变量。

抛出异常:throw 表达式
捕获异常

    try{被检查的语句}
    catch(异常信息类型[变量名]{进行异常处理的语句}
捕获指定类型的异常并处理,处理后程序从最后一个catch块后继续运行。
被throw抛出的异常数据不受作用范围限制,直到捕获被处理为止。因此捕获可以通过引用用来访问异常数据。即使已经被处理的异常数据,也可以用一个不带数据的throw重新抛出。

set_terminate()

异常传递机制:从throw 开始离开正常执行流程,再ben函数内找到有没有包含它的try{}快,如果有,就依次找它的Catch快知道第一个类型匹配的为止进入这个catch块执行处理,之后从最后一个catch快继续执行,如果没有包含它的try{}块或者没有类型匹配的catch块,异常数据继续传递到上层函数(调用它的那个函数)中重复这个过程,如果直到main函数还没有处理完成,系统就调用terminate终止程序。
多个catch块,如果同时含父子类型,应该把子类类型放在前面,如果有一个是catch(…),他必须放在最后。

异常声明:一个函数可以声明自己可能抛出那些类型的异常,格式:

返回类型 函数名(参数表)throw(异常类型列表)如:int func(int a[],int idx)throw(double,int,string);

如果抛出异常超出列表,引发意外通过设置set_unexpected(func)来调用func处理意外情况。
没有异常声明表示可能抛出任何类型的异常,空异常列表(无参数),实际上并不执行throw语句,并不抛出任何异常信息,程序将非正常停止。

main函数里传参数:argument参数,count数量,vector不定长数组。main(int argc,char* argv[])

标准异常#include
定义子类覆盖父类的虚函数时,异常声明抛出的内容不能多于父类中这个函数异常声明内容。

标准库中抛出的异常类型都是exceprion类的子类。
自己定义异常类型一般继承exception类,覆盖const char* what(),const throw()函数,返回一个字符串,描述发生了什么异常。

I/O 接口:

标准库的输入输出对象
cin 控制台输入 console input(标准输入)
cout 控制台输出 console output(标准输出)
cerr 错误输出,不缓存不重定向。
clog 跟Cerr一样

输入:

格式化输入
把一系列字符转换成对应格式的数值保存在变量中。
ws去掉前导的空白字符,(空格,制表符,换行符,回车符,换页符,垂直制表符),hex,dec,oct表示用几进制输入;

cin>>ws>>hex>>n;

非格式化输入
保持原来的格式:
get(),get(char&) 从输入流读取一个字符。空白字符也照样读,
peek() 查看但不取走,
putback(char) 把一个刚取走的字符送回输入流中(如果送回的不是刚取走的字符,系统不保证成功)。
ignore(int n=1,char end =EOF) 从输入流中丢弃n个字符,直到遇到end为止。
getline(char 数组,最大长度) 读取一行输入,如果输入超过最大长度,只读取最大长度-1个字符并追加“\0”,并设置错误标志。出于错误状态下的IO流对象不再执行IO操作,直到clear()清除错误状态为止,clear只清除错误状态,并不丢输入流中的字符,像丢弃输入流中的字符用ignore。
getline(cin,string对象) 则不存在超长问题。秘密:IO流对象可以看成一个bool类型数据。当处于正常状态时为真,出于错误状态时为假。
while(cin){//输入出错是循环结束(不匹配,ctrl+D)}
getline 可以指定第三个参数指定读到什么字符为止。
cin.getline(buf,maxlen,endchar);
getline(cin,stringobject,end_char);
格式化输出:内存数值,字符显示。
setf()/unsetf(flag) 设置取消标志。标志在ios中定义,使用ios::标志。
boolalpha,dex/hex/oct三选一,left/right/internal三选一,scintific/fixed二选一科学计数法/小数形式,
showbase 显示显示前缀0o/0/无,
showpoint 显示小数点,
showpos 正数显示正号。
unnpercase 在输出十六进制数时的X和a-f以及科学计数法中的e用大写字母,
unibuf 每次输出后都刷新缓冲区。
skipws 输入时跳过空白字符。一般使用格式控制符取代标志。
width(w) 指定下一项输出占多少个字符的位置,如果实际数据超过指定宽度按实际数据看度输出而不截断,而宽度只对下一项有效,实际数据短则用空格填充。
fill(c) 可以指定用其他字符代替空格填充。
非格式化输出

推荐参考文献

<>
<< C Primer Plus>>

<< c和指针>>
<

1.《C++ Primer》:全面学习C++的必备书籍《C和指针》
2.《Thinking in C++》:了解C++背后的一些机制
3.《深度探索C++对象模型》:深入了解C++,也是学习COM的必经之路

你可能感兴趣的:(c++)