C++自学摘要
2016-04-10 16:20
357 查看
1.void类型指针(随机类型指针)的抽象化应用
memcpy内存拷贝函数
void *memcpy(void *dest,const void *src,size)
因为memcpy函数是对内存进行处理的,起始地址是src指针对应的首地址,要拷贝的内容长度是size
拷贝的目的地是dest随机指针,在这里使用void指针的一大好处就是,我们无需考虑src首地址的具体数据结构
只需要拷贝其内容就可以了,我们最后只需要对dest进行强制类型转换就可以输出我们拷贝的内容了
2.用引用,可以使函数调用作为左值.引用表达式是一个左值表达式,因此它可以出现在形、实参数的任何一方。若一个函数返回了引用,那么该函数的调用也可以被赋值。一般,当返搜索回值不是本函数内定义的局部变量时就可以返回一个引用。在通常情况下,引用返回值只用在需要对函数的调用重新赋值的场合,也就是对函数的返回值重新赋值的时候。避免将局部作用域中变时的地址返回,就使用函数调用表达式全为左值来使用。
3.复制构造函数的一些小细节
#include"iostream"
#include"cstdio"
#include"cstring"
using namespace std;
class Person
{
public:
Person(Person &personbud);
Person(char *k,int p);
~Person();
void show()
{
cout<<"name:"<<name<<endl; //cout<<p;输出完整的字符串,而cout<<*p;输出字符串首字符
cout<<"age:"<<age<<endl;
}
private:
int age;
char *name;
};
Person::Person(Person &person0)
{
age=person0.age;
name=new char[strlen(person0.name)+1];
strcpy(name,person0.name);
cout<<"对象成员赋值成功"<<endl;
}
Person::Person(char *k,int p)
{
name=new char[strlen(k)+1];
strcpy(name,k);
age=p;
cout<<"成功调用构造函数"<<endl;
}
Person::~Person()
{
delete name;
cout<<"成功调用析构函数"<<endl;
}
int main()
{
Person person1("zhangsan",18),person2(person1);
person1.show();
person2.show();
return 0;
}
4.关于公有函数作为对外接口的一点小认识
#include"iostream"
#include"cstdio"
using namespace std;
class T
{
public:
int get()
{
return x;
};
T(int w):x(w){};
T(const T &t)
{
x=t.x;
cout<<"复制对象成功"<<endl;
}
private:
int x;
};
void show(T &t)
{
cout<<t.get()<<endl; //此时如果输出t.x则报错,毕竟外部成员函数无法访问私有成员,但是可以用共有函数作为输出接口
}
int main()
{
T t(5);
show(t);
return 0;
}
5.
在C++的继承方式上来说,我们可以这么理解,继承方式确定之后,成员类型权限不增但是会减,相同的会保持
6.
我们一旦限制继承权限是私有继承的时候,原基类的所有成员在派生类中全部视为私有成员
也就是说我们可以通过派生类中的公有成员函数来对其进行访问,但是我们在派生类外进行访问则是非法的
eg:obj.setx()//setx是私有继承基类中的成员变量,这种写法是错误的
obj.set()//set(){return setx()}//这是可以的
7.私有类成员和保护类成员的主要区别在于
私有类成员无论是在基类还是在派生类中属性都是私有的,都是不能被基类和派生类直接访问的,但是保护类成员的含义就是
8.在继承中,私有成员无论是何种继承方式均变成不可访问,我们获取不可访问的变量的信息唯一的方法就是通过基类中的公有或者受保护类成员作为接口间接输出或者间接修改他的值(是可以修改的)。但是受保护成员在派生类中对外而言有私有成员不可访问的性质,但是在对内而言受保护成员在派生类中可以被访问。(这是核心的区别)
9.举例,基类中的基类私有变量虽然在派生类中我们是无法直接访问的,但是我们可以通过派生类中的公有成员函数调用基类中的公有成员函数(虽然此时在派生类中此共有函数如果是私有继承的话是私有变量,但是公有成员函数可以调用私有成员,所以,是成立的),通过基类的公有成员函数实现对此时在派生类中不可访问的变量进行个访问和修改。
#include"iostream"
#include"cstdio"
using namespace std;
class math
{
private:
int x;
public:
math()
{
x=0;
cout<<"构造函数开始调用"<<endl;
}
~math()
{
cout<<"析构函数开始调用"<<endl;
}
void setx(int y)
{
x=y;
}
void showx()
{
cout<<x<<endl;
}
int getx()
{
return x;
}
};
class m:private math
{
public:
m()
{
cout<<"派生类默认构造函数开始调用"<<endl;
}
m(int w)
{
set(w);
cout<<"派生类构造函数开始调用"<<endl;
}
void show()
{
cout<<getx()<<endl;
}
void set(int a)
{
setx(a);
}
};
int main()
{
m h;
h.show();
m z(10);
z.show();
h.set(20);
h.show();
return 0;
}
10.
记住派生类和基类的构造函数和析构函数调用的顺序
开始的时候:先调用基类的构造函数在调用派生类的构造函数
结束的时候:先调用派生类的析构函数在调用基类的构造函数
11.
虚函数和虚析构函数的意义
1.为什么基类的析构函数是虚函数?
在实现多态时,当用基类操作派生类,在析构时防止只析构基类而不析构派生类的状况发生。
(因为此时使用基类指针声明派生类对象,所以析构的时候,只析构基类的类型,而构造的派生类并不析构)
下面转自网络:源地址 http://blog.sina.com.cn/s/blog_7c773cc50100y9hz.html
a.第一段代码
复制代码
复制代码
#include<iostream>
using namespace std;
class ClxBase{
public:
ClxBase() {};
~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;};
void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};
class ClxDerived : public ClxBase{
public:
ClxDerived() {};
~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
};
int main(){
ClxDerived *p = new ClxDerived;
p->DoSomething();
delete p;
return 0;
}
复制代码
复制代码
运行结果:
Do something in class ClxDerived!
Output from the destructor of class ClxDerived!
Output from the destructor of class ClxBase!
这段代码中基类的析构函数不是虚函数,在main函数中用继承类的指针去操作继承类的成员,释放指针P的过程是:先释放继承类的资源,再释放基类资源.
b.第二段代码
复制代码
复制代码
#include<iostream>
using namespace std;
class ClxBase{
public:
ClxBase() {};
~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;};
void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};
class ClxDerived : public ClxBase{
public:
ClxDerived() {};
~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
void DoSomething() { cout << "Do something in class ClxDerived!" << endl; }
};
int main(){
ClxBase *p = new ClxDerived;
p->DoSomething();
delete p;
return 0;
}
复制代码
复制代码
输出结果:
Do something in class ClxBase!
Output from the destructor of class ClxBase!
这段代码中基类的析构函数同样不是虚函数,不同的是在main函数中用基类的指针去操作继承类的成员,释放指针P的过程是:只是释放了基类的资源,而没有调用继承类的析构函数.调用 dosomething()函数执行的也是基类定义的函数.
一般情况下,这样的删除只能够删除基类对象,而不能删除子类对象,形成了删除一半形象,造成内存泄漏.
在公有继承中,基类对派生类及其对象的操作,只能影响到那些从基类继承下来的成员.如果想要用基类对非继承成员进行操作,则要把基类的这个函数定义为虚函数.
********析构函数自然也应该如此:如果它想析构子类中的*重新定义或新的成员及对象*,当然也应该声明为虚的.
c.第三段代码:
复制代码
复制代码
#include<iostream>
using namespace std;
class ClxBase{
public:
ClxBase() {};
virtual ~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;};
virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};
class ClxDerived : public ClxBase{
public:
ClxDerived() {};
~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
};
int main(){
ClxBase *p = new ClxDerived;
p->DoSomething();
delete p;
return 0;
}
复制代码
复制代码
运行结果:
Do something in class ClxDerived!
Output from the destructor of class ClxDerived!
Output from the destructor of class ClxBase!
这段代码中基类的析构函数被定义为虚函数,在main函数中用基类的指针去操作继承类的成员,释放指针P的过程是:只是释放了继承类的资源,再调用基类的析构函数.调用dosomething()函数执行的也是继承类定义的函数.
如果不需要基类对派生类及对象进行操作,则不能定义虚函数,因为这样会增加内存开销.当类里面有定义虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间.所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数.
C++编程语言是一款应用广泛,支持多种程序设计的计算机编程语言。我们今天就会为大家详细介绍其中C++多态性的一些基本知识,以方便大家在学习过程中对此能够有一个充分的掌握。
多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。多态(polymorphism),字面意思多种形状。
C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。(这里我觉得要补充,重写的话可以有两种,直接重写成员函数和重写虚函数,只有重写了虚函数的才能算作是体现了C++多态性)而重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。编译器会根据这些函数的不同列表,将同名的函数的名称做修饰,从而生成一些不同名称的预处理函数,来实现同名函数调用时的重载问题。但这并没有体现多态性。
***************重载和多态有区别,前者是同时存在多种状态,应用时选择一个,后者是在应用时覆盖,屏蔽原有的**********
多态与非多态的实质区别就是函数地址是早绑定还是晚绑定。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的,就是说地址是早绑定的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定。
那么多态的作用是什么呢,封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。
最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。因为没有多态性,函数调用的地址将是一定的,而固定的地址将始终调用到同一个函数,这就无法实现一个接口,多种方法的目的了。
13。类内函数调用数据成员合法
14.对于等于运算符的重载,有些许注意要点
#include"iostream"
#include"cstdio"
using namespace std;
class math
{
public:
void show()
{
cout<<*p<<endl;
}
math()
{
p=new int(0);
cout<<"默认构造函数调用成功"<<endl;
}
math(int x)
{
p=new int;
*p=x;
cout<<"构造函数调用成功"<<endl;
}
~math()
{
delete p;
cout<<"析构函数调用成功"<<endl;
}
math& operator=(math k)
{
*p=*k.p;
return *this;
}
private:
int *p;
};
int main()
{
math x,y(1),z(4);
x.show();
x=y;y
x.show();
return 0;
}
如果使用引用接收引用返回值,则返回的引用必须具有较长的生存期,不可以引用局部变量。
如果使用引用接收值返回值,则引用了一个临时对象,该对象的生存期将延长到和这个引用相同
C:\Users\asuspc\AppData\Roaming\360se6\Application\360se.exe http://blog.csdn.net/duanruibupt/article/details/6881018 /////该网址成功解释了引用的区别
15.
C++的流插入运算符“<<”和流提取运算符“>>”是C++在类库中提供的,所有C++编译系统都在类库中提供输入流类istream和输出流类ostream。cin和cout分别是istream类和ostream类的对象。在类库提供的头文件中已经对“<<”和“>>”进行了重载,使之作为流插入运算符和流提取运算符,能用来输出和输入C++标准类型的数据。因此,凡是用“cout<<”和“cin>>”对标准类型数据进行输入输出的,都要用#include 把头文件包含到本程序文件中。
用户自己定义的类型的数据,是不能直接用“<<”和“>>”来输出和输入的。如果想用它们输出和输入自己声明的类型的数据,必须对它们重载。
对“<<”和“>>”重载的函数形式如下:
istream & operator >> (istream &, 自定义类 &);
ostream & operator << (ostream &, 自定义类 &);
即重载运算符“>>”的函数的第一个参数和函数的类型都必须是istream&类型,第二个参数是要进行输入操作的类。重载“<<”的函数的第一个参数和函数的类型都必须是ostream&类型,第二个参数是要进行输出操作的类。因此,只能将重载“>>”和“<<”的函数作为友元函数或普通的函数,而不能将它们定义为成员函数。
16.
请思考,return output的作用是什么?回答是能*连续向输出流插入信息*。output是ostream类的对象,它是实参cout的引用,也就是cout通过传送地址给output,使它们二者共享同一段存储单元,或者说output是cout的别名。因此,return output就是return cout,将输出流cout的现状返回,即保留输出流的现状。
还有一点要说明,在本程序中,在Complex类中定义了运算符“<<”重载函数为友元函数,因此只有在输出Complex类对象时才能使用重载的运算符,对其他类型的对象是无效的。如
cout<<time1; //time1是Time类对象,不能使用用于Complex类的重载运算符
17.
可以看到,在运算符重载中使用引用(reference)的重要性。利用引用作为函数的形参可以在调用函数的过程中不是用传递值的方式进行虚实结合,而是通过传址方式使形参成为实参的别名,因此不生成临时变量(实参的副本),减少了时间和空间的开销。此外,如果重载函数的返回值是对象的引用时,返回的不是常量,而是引用所代表的对象,它可以出现在赋值号的左侧而成为左值(left value),可以被赋值或参与其他操作(如保留cout流的当前值以便能连续使用“<<”输出)。但使用引用时要特别小心,因为修改了引用就等于修改了它所代表的对象。
18.
在进行类型转换的重载运算符的时候,我们不要在行参中用引用,因为一旦要对其他基本的数据类型转换为类的类型的时候,引用的对象有可能是基本的数据类型,但是引用代表的含义是对类进行取地址,但是此时数据类型不是类,就会报错
19.
多参数模板的返回值可能会出出现结果错误,但不是程序引起的,而是返回值的类型决定的
20.
模板必须是要有顺序的,是按顺序排列的
争取代码示例:
#include"iostream"
#include"cstdio"
using namespace std;
template<class T,int size>
void sort(T a[size])
{
cout<<"yes"<<endl;
}
int main()
{
int a[]={0,1,2,3,4,5,6,7,8,9};
sort<int ,10>(a); //核心
return 0;
}
错误代码示例:
#include"iostream"
#include"cstdio"
using namespace std;
template<class T,int size>
void sort(T a[size])
{
cout<<"yes"<<endl;
}
int main()
{
int a[]={0,1,2,3,4,5,6,7,8,9};
sort<10,int>(a); //核心
return 0;
}
应用引用的方式就可以不用显式的指定参数了
#include"iostream"
#include"cstdio"
using namespace std;
template<class T,int size>
void sort(T (&a)[size])
{
cout<<"yes"<<endl;
}
int main()
{
int a[]={0,1,2,3,4,5,6,7,8,9};
sort(a);
return 0;
}
21.
模板类型基本数据类型或者自己定义的类
22.
string类型变量在C++的#include"string"头文件里面
23.
用模板类实现栈
#include"iostream"
#include"cstdio"
using namespace std;
template<class T>
class stack
{
public:
stack()
{
top=0;
}
bool stackempty()
{
return top==0;
}
bool stackfull()
{
return top==99;
}
void stackpush(T a)
{
top++;
data[top]=a;
}
T stackpop()
{
top--;
return data[top+1];
}
private:
int top;
T data[100];
};
int main()
{
stack<int> int_stack;
stack<char> char_stack;
for(int i=1;i<=10;i++)
{
int_stack.stackpush(i);
}
while(!int_stack.stackempty())
{
cout<<int_stack.stackpop()<<' ';
}
cout<<endl;
char_stack.stackpush('A');
char_stack.stackpush('B');
char_stack.stackpush('C');
while(!char_stack.stackempty())
{
cout<<char_stack.stackpop()<<' ';
}
cout<<"End"<<endl;
return 0;
}
23
#include"iostream"
#include"cstdio"
using namespace std;
template<class T,size_t size>
class stack
{
public:
stack()
{
top=0;
}
bool stackempty()
{
return top==0;
}
bool stackfull()
{
return top==size;
}
T stackpop()
{
top--;
return data[top+1];
}
void stackpush(T a)
{
top++;
return data[top]=a;
}
private:
T data[size];
size_t top;
};
template<class T,size_t size,template<class U,size_t len> class stack>
class container
{
public:
void show()
{
cout<<kstack.stackpop()<<endl;
}
private:
stack<T,size> kstack;
};
int main()
{
container<int,10,stack> contain;
contain.show();
return 0;
}
以上是在container模板类中声明了一个固定模板参数表的私有成员的模板类成员,并用前面定义的stack来替换contanier中的模板参数类,实现对container的实例化
24.
组成的库来说提供了更好的代码重用机会。在C++标准中,STL被组织为下面的13个头文件:<algorithm>、<deque>、<functional>、<iterator>、<vector>、<list>、<map>、
<memory>、<numeric>、<queue>、<set>、<stack>和<utility>。
<algorithm>是所有STL头文件中最大的一个(尽管它很好理解),它是由一大堆模版函数组成的,可以认为每个函数在很大程度上都是独立的,其中常用到的功能范围涉及到比较、交换、查找、遍历操作、复制、修改、移除、反转、排序、合并等等。
<numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数,包括加法和乘法在序列上的一些操作。
<functional>中则定义了一些模板类,用以声明函数对象。
25.
50条忠告:(其中有几条觉得写的不够贴切,所以删了,发了余下的部分)
1.把C++当成一门新的语言学习;
2.看《Thinking In C++》,不要看《C++变成死相》;
3.看《The C++ Programming Language》和《Inside The C++ Object Model》,不要因为他们很难而我们自己是初学者所以就不看;
4.不要被VC、BCB、BC、MC、TC等词汇所迷惑——他们都是集成开发环境,而我们要学的是一门语言;
5.不要放过任何一个看上去很简单的小编程问题——他们往往并不那么简单,或者可以引伸出很多知识点;
6.会用Visual C++,并不说明你会C++;
7.学class并不难,template、STL、generic programming也不过如此——难的是长期坚持实践和不遗余力的博览群书;
8.如果不是天才的话,想学编程就不要想玩游戏——你以为你做到了,其实你的C++水平并没有和你通关的能力一起变高——其实可以时刻记住:学C++是为了编游戏的;
9.看Visual C++的书,是学不了C++语言的;
16.把时髦的技术挂在嘴边,还不如把过时的技术记在心里;
18.学习编程最好的方法之一就是阅读源代码;
19.在任何时刻都不要认为自己手中的书已经足够了;
20.请阅读《The Standard C++ Bible》(中文版:标准C++宝典),掌握C++标准;
21.看得懂的书,请仔细看;看不懂的书,请硬着头皮看;
22.别指望看第一遍书就能记住和掌握什么——请看第二遍、第三遍;
23.请看《Effective C++》和《More Effective C++》以及《Exceptional C++》;
24.不要停留在集成开发环境的摇篮上,要学会控制集成开发环境,还要学会用命令行方式处理程序;
25.和别人一起讨论有意义的C++知识点,而不是争吵XX行不行或者YY与ZZ哪个好;
26.请看《程序设计实践》,并严格的按照其要求去做;
27.不要因为C和C++中有一些语法和关键字看上去相同,就认为它们的意义和作用完全一样;
28.C++绝不是所谓的C的“扩充”——如果C++一开始就起名叫Z语言,你一定不会把C和Z语言联系得那么紧密;
29.请不要认为学过XX语言再改学C++会有什么问题——你只不过又在学一门全新的语言而已;
30.读完了《Inside The C++ Object Model》以后再来认定自己是不是已经学会了C++;
31.学习编程的秘诀是:编程,编程,再编程;
32.请留意下列书籍:《C++面向对象高效编程(C++ Effective Object-Oriented Software Construction)》《面向对象软件构造(Object-Oriented Software Construction)》《设计模式(Design Patterns)》《The Art of Computer Programming》;
34.请把书上的程序例子亲手输入到电脑上实践,即使配套光盘中有源代码;
35.把在书中看到的有意义的例子扩充;
36.请重视C++中的异常处理技术,并将其切实的运用到自己的程序中;
37.经常回顾自己以前写过的程序,并尝试重写,把自己学到的新知识运用进去;
38.不要漏掉书中任何一个练习题——请全部做完并记录下解题思路;
39.C++语言和C++的集成开发环境要同时学习和掌握;
40.既然决定了学C++,就请坚持学下去,因为学习程序设计语言的目的是掌握程序设计技术,而程序设计技术是跨语言的;
41.就让C++语言的各种平台和开发环境去激烈的竞争吧,我们要以学习C++语言本身为主;
42.当你写C++程序写到一半却发现自己用的方法很拙劣时,请不要马上停手;请尽快将余下的部分粗略的完成以保证这个设计的完整性,然后分析自己的错误并重新设计和编写(参见43);
43.别心急,设计C++的class确实不容易;自己程序中的class和自己的class设计水平是在不断的编程实践中完善和发展的;
44.决不要因为程序“很小”就不遵循某些你不熟练的规则——好习惯是培养出来的,而不是一次记住的;
45.每学到一个C++难点的时候,尝试着对别人讲解这个知识点并让他理解——你能讲清楚才说明你真的理解了;
46.记录下在和别人交流时发现的自己忽视或不理解的知识点;
47.请不断的对自己写的程序提出更高的要求,哪怕你的程序版本号会变成Version 100.XX;
48.保存好你写过的所有的程序——那是你最好的积累之一;
49.请不要做浮躁的人;
50.请热爱C++!
第四部分:
C++头文件一览
C、传统 C++
#include <assert.h> 设定插入点
#include <ctype.h> 字符处理
#include <errno.h> 定义错误码
#include <float.h> 浮点数处理
#include <fstream.h> 文件输入/输出
#include <iomanip.h> 参数化输入/输出
#include <iostream.h> 数据流输入/输出
#include <limits.h> 定义各种数据类型最值常量
#include <locale.h> 定义本地化函数
#include <math.h> 定义数学函数
#include <stdio.h> 定义输入/输出函数
#include <stdlib.h> 定义杂项函数及内存分配函数
#include <string.h> 字符串处理
#include <strstrea.h> 基于数组的输入/输出
#include <time.h> 定义关于时间的函数
#include <wchar.h> 宽字符处理及输入/输出
#include <wctype.h> 宽字符分类
标准 C++
#include <algorithm> 通用算法
#include <bitset> 位集容器
#include <cctype>
#include <cerrno>
#include <clocale>
#include <cmath>
#include <complex> 复数类
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque> 双端队列容器
#include <exception> 异常处理类
#include <fstream>
#include <functional> 定义运算函数(代替运算符)
#include <limits>
#include <list> 线性列表容器
#include <map> 映射容器
#include <iomanip>
#include <ios> 基本输入/输出支持
#include <iosfwd> 输入/输出系统使用的前置声明
#include <iostream>
#include <istream> 基本输入流
#include <ostream> 基本输出流
#include <queue> 队列容器
#include <set> 集合容器
#include <sstream> 基于字符串的流
#include <stack> 堆栈容器
#include <stdexcept> 标准异常类
#include <streambuf> 底层输入/输出支持
#include <string> 字符串类
#include <utility> 通用模板类
#include <vector> 动态数组容器
#include <cwchar>
#include <cwctype>
C99 增加
#include <complex.h> 复数处理
#include <fenv.h> 浮点环境
#include <inttypes.h> 整数格式转换
#include <stdbool.h> 布尔环境
#include <stdint.h> 整型环境
#include <tgmath.h> 通用类型数学宏
25.流式输入输出常用的函数
cout.put()将单个字符插入到缓存中,可以实现连续的插入
#include"iostream"
#include"cstdio"
using namespace std;
int main()
{
cout.put(65).put(78).put(45)<<endl;
return 0;
}
cout.write(字符串首地址,输出长度)将字符串整体输出或者输出字符串的部分内容
#include"iostream"
#include"cstdio"
using namespace std;
int main()
{
char a[]="lantian";
cout.write(a,7)<<endl;
cout.write(a+1,6);
return 0;
}
26.C++缓存和流式输入输出
实际上,在内存中为每一个数据流开辟一个内存缓冲区,用来存放流中的数据。当用cout和插入运算符“<<”向显示器输出数据时,先将这些数据送到程序中的输出缓冲区保存,直到缓冲区满了或遇到endl,就将缓冲区中的全部数据送到显示器显示出来。在输入时,从键盘输入的数据先放在键盘缓冲区中,当按回车键时,键盘缓冲区中的数据输入到程序中的输入缓冲区,形成cin流,然后用提取运算符“>>”从输入缓冲区中提取数据送给程序中的有关变量。总之,流是与内存缓冲区相对应的,或者说,缓冲区中的数据就是流。
27.文件读取和写入实例
#include"iostream"
#include"cstdio"
#include"cstdlib"
#include"fstream"
using namespace std;
struct node
{
char name[20];
char sex[10];
int age;
};
typedef struct node p;
p student[3]={"lantian","man",19,"baba","man",45,"mama","woman",46};
p stud;
ostream& operator<<(ostream& out,p stu)
{
out<<stu.name<<endl;
out<<stu.sex<<endl;
out<<stu.age<<endl;
return out;
}
int main()
{
ofstream outfile("student.txt",ios::trunc);
for(int i=0;i<=2;i++)
{
outfile.write((char*)&student[i],sizeof(student[i]));
}
outfile.close();
ifstream infile("student.txt",ios::in);
for(int i=0;i<=2;i++)
{
infile.read((char*)&stud,sizeof(stud));
cout<<stud<<endl;
}
infile.close();
return 0;
}
27.对于二进制文件的重要应用函数,指针转移函数
输入:seekg(偏移量,正代表向右,负代表向左,ios::beg(指针移到起点)//ios::cur(指针在原处)//ios::end(指针在终点))
tellg()
输出:seekp()同上
tellp()
28.文件读取综合实例
#include"iostream"
#include"cstdio"
#include"fstream"
#include"cstdlib"
using namespace std;
struct node
{
char name[20];
char sex[10];
int age;
};
typedef struct node point;
point student[3]={"lantian","man",19,"mama","woman",46,"baba","man",45};
point stud[2];
point k;
ostream& operator<<(ostream& out,point k)
{
cout<<k.name<<endl;
cout<<k.sex<<endl;
cout<<k.age<<endl;
return out;
}
int main()
{
fstream iofile("student.txt",ios::in|ios::out|ios::trunc|ios::binary);
if(!iofile)
{
cerr<<"error!"<<endl;
exit(0);
}
for(int i=0;i<=2;i++)
{
iofile.write((char*)&student[i],sizeof(student[i]));
}
for(int i=0;i<=2;i++)
{
iofile.seekg(0,ios::beg);
iofile.read((char*)&k,sizeof(k));
cout<<k<<endl;
}
cout<<"***********************"<<endl;
iofile.seekg(0,ios::beg);
for(int i=0,t=0;i<=2;i=i+2,t++)
{
iofile.seekg(i*sizeof(k),ios::beg);
iofile.read((char*)&stud[t],sizeof(k));
cout<<stud[t]<<endl;
cout<<"begin to change!"<<endl;
cin>>stud[t].name>>stud[t].sex>>stud[t].age;
iofile.seekp(i*sizeof(k),ios::beg);
iofile.write((char*)&stud[t],sizeof(k));
}
iofile.seekg(0,ios::beg);
for(int i=0;i<=2;i++)
{
iofile.read((char*)&k,sizeof(k));
cout<<k<<endl;
}
iofile.close();
return 0;
}
27.流式输入
ch=cin.get()从输入流中获取一个字符,返回值是读入的字符,文件结束返回EOF
cin.get(char (&(可有可无))ch)将从流中提取的字符赋值给指定的字符变量
#include"iostream"
#include"cstdio"
using namespace std;
int main()
{
char a[20];
cin.get(a,5,'x'); //读取5-1个字符,x为结束标志,如果扫描到x提前读入结束
cout<<a<<endl;
return 0;
}
cin.getline(char *buf,int limit,deline='\n'(默认的结束符)) 与上面的类似,读取一行的内容
cin.read(char *buf,int size) //向指定的内容中输入固定数目的内容
cin.eof()文件判空
cin.peek()检查该字符的下一个字符,但是注意指针并没有移动,只是检查下一个字符,指针仍停留在当前字符处
cin.putback(ch)将字符ch重新插入到缓冲区的指针位置
cin.ignore(int n=1,char ch=EOF) 等号是默认字符和值的意思,在没有函数的参数的时候,应用默认值,否则应用函数的参数的值,代表跳过n个字符或者知道遇到ch字符的时候,提起包括ch字符都跳过(此跳过值得是指针的移动)
28.
try-catch语句的调用规则,如果在同一函数中的话,直接找本函数,如果本函数没,返回他的调用层继续找,知道最后还没有找到的话,返回程序报错异常处理机制
29.析构函数和异常处理
#include"iostream"
#include"cstdio"
using namespace std;
class aa
{
public:
aa()
{
a=0;
cout<<"construct"<<endl;
}
~aa()
{
cout<<"destruct"<<endl;
}
void setdata(int k)
{
if(k>=100) throw k;
a=k;
cout<<"change successfully"<<endl;
}
private:
int a;
};
int main()
{
aa wa;
try
{
wa.setdata(111);
}
catch(int d)
{
cout<<"error!"<<endl;
cout<<"throw:"<<d<<endl;
}
return 0;
}
#include"iostream"
#include"cstdio"
using namespace std;
class aa
{
public:
aa()
{
a=0;
cout<<"construct"<<endl;
}
~aa()
{
cout<<"destruct"<<endl;
}
void setdata(int k)
{
if(k>=100) throw k;
a=k;
cout<<"change successfully"<<endl;
}
private:
int a;
};
int main()
{
try
{
aa wa; //如果类对象是在try语句块中进行定义的,那么在出现异常情况的时候,立即离开try语句块(如果是函数且对象在try内定义,那么立即进入catch语句块进行一场处理)
wa.setdata(111);
}
catch(int d)
{
cout<<"error!"<<endl;
cout<<"throw:"<<d<<endl;
}
return 0;
}
简而言之,只要try语句块中出现了对对象的声明的话,只要出现了一场(只要开始throw)必须要调用先调用析构函数然后再转入catch语句块中进行一场处理
30.
在符合继承关系中要小心这么一种情况
base1
base2 base3
base4
base2,3分别从base1中继承,而base4从base2和base3中继承,这时候在base4的构建时
我们先调用base1的构造函数在调用base2,3的构造函数,最后调用base4的构造函数,所以如果base4中没有对base1进行初始化操作,那么会导致base1先调用默认的构造函数,在调用base2,base3的构造函数,但是base2,3中的构造函数会先调用base1的构造函数,这时候和之前就会出现矛盾,所以我们规定,在这种情况的时候base4中也要写出base1的构造函数的调用语句
31.
私有成员
1.在基类的作用域中私有始终是私有,其他的类型变量也都不会改变属性
2.在派生类的作用域中基类的私有是不可访问
3.但是,我们可以通过基类的公有函数作为接口来间接的调用所谓的不可访问变量
再者,对于私有继承
虽然基类的共有函数变成派生类中的私有函数,但是派生类中的共有函数可以调用私有成员,并且在基类的作用域中,共有函数还是公有,还是可以调用私有成员变量的,所以,这种情况下我门还是可以以基类的共有函数做接口调用到在派生类中视为不可访问变量的私有成员
memcpy内存拷贝函数
void *memcpy(void *dest,const void *src,size)
因为memcpy函数是对内存进行处理的,起始地址是src指针对应的首地址,要拷贝的内容长度是size
拷贝的目的地是dest随机指针,在这里使用void指针的一大好处就是,我们无需考虑src首地址的具体数据结构
只需要拷贝其内容就可以了,我们最后只需要对dest进行强制类型转换就可以输出我们拷贝的内容了
2.用引用,可以使函数调用作为左值.引用表达式是一个左值表达式,因此它可以出现在形、实参数的任何一方。若一个函数返回了引用,那么该函数的调用也可以被赋值。一般,当返搜索回值不是本函数内定义的局部变量时就可以返回一个引用。在通常情况下,引用返回值只用在需要对函数的调用重新赋值的场合,也就是对函数的返回值重新赋值的时候。避免将局部作用域中变时的地址返回,就使用函数调用表达式全为左值来使用。
3.复制构造函数的一些小细节
#include"iostream"
#include"cstdio"
#include"cstring"
using namespace std;
class Person
{
public:
Person(Person &personbud);
Person(char *k,int p);
~Person();
void show()
{
cout<<"name:"<<name<<endl; //cout<<p;输出完整的字符串,而cout<<*p;输出字符串首字符
cout<<"age:"<<age<<endl;
}
private:
int age;
char *name;
};
Person::Person(Person &person0)
{
age=person0.age;
name=new char[strlen(person0.name)+1];
strcpy(name,person0.name);
cout<<"对象成员赋值成功"<<endl;
}
Person::Person(char *k,int p)
{
name=new char[strlen(k)+1];
strcpy(name,k);
age=p;
cout<<"成功调用构造函数"<<endl;
}
Person::~Person()
{
delete name;
cout<<"成功调用析构函数"<<endl;
}
int main()
{
Person person1("zhangsan",18),person2(person1);
person1.show();
person2.show();
return 0;
}
4.关于公有函数作为对外接口的一点小认识
#include"iostream"
#include"cstdio"
using namespace std;
class T
{
public:
int get()
{
return x;
};
T(int w):x(w){};
T(const T &t)
{
x=t.x;
cout<<"复制对象成功"<<endl;
}
private:
int x;
};
void show(T &t)
{
cout<<t.get()<<endl; //此时如果输出t.x则报错,毕竟外部成员函数无法访问私有成员,但是可以用共有函数作为输出接口
}
int main()
{
T t(5);
show(t);
return 0;
}
5.
在C++的继承方式上来说,我们可以这么理解,继承方式确定之后,成员类型权限不增但是会减,相同的会保持
6.
我们一旦限制继承权限是私有继承的时候,原基类的所有成员在派生类中全部视为私有成员
也就是说我们可以通过派生类中的公有成员函数来对其进行访问,但是我们在派生类外进行访问则是非法的
eg:obj.setx()//setx是私有继承基类中的成员变量,这种写法是错误的
obj.set()//set(){return setx()}//这是可以的
7.私有类成员和保护类成员的主要区别在于
私有类成员无论是在基类还是在派生类中属性都是私有的,都是不能被基类和派生类直接访问的,但是保护类成员的含义就是
8.在继承中,私有成员无论是何种继承方式均变成不可访问,我们获取不可访问的变量的信息唯一的方法就是通过基类中的公有或者受保护类成员作为接口间接输出或者间接修改他的值(是可以修改的)。但是受保护成员在派生类中对外而言有私有成员不可访问的性质,但是在对内而言受保护成员在派生类中可以被访问。(这是核心的区别)
9.举例,基类中的基类私有变量虽然在派生类中我们是无法直接访问的,但是我们可以通过派生类中的公有成员函数调用基类中的公有成员函数(虽然此时在派生类中此共有函数如果是私有继承的话是私有变量,但是公有成员函数可以调用私有成员,所以,是成立的),通过基类的公有成员函数实现对此时在派生类中不可访问的变量进行个访问和修改。
#include"iostream"
#include"cstdio"
using namespace std;
class math
{
private:
int x;
public:
math()
{
x=0;
cout<<"构造函数开始调用"<<endl;
}
~math()
{
cout<<"析构函数开始调用"<<endl;
}
void setx(int y)
{
x=y;
}
void showx()
{
cout<<x<<endl;
}
int getx()
{
return x;
}
};
class m:private math
{
public:
m()
{
cout<<"派生类默认构造函数开始调用"<<endl;
}
m(int w)
{
set(w);
cout<<"派生类构造函数开始调用"<<endl;
}
void show()
{
cout<<getx()<<endl;
}
void set(int a)
{
setx(a);
}
};
int main()
{
m h;
h.show();
m z(10);
z.show();
h.set(20);
h.show();
return 0;
}
10.
记住派生类和基类的构造函数和析构函数调用的顺序
开始的时候:先调用基类的构造函数在调用派生类的构造函数
结束的时候:先调用派生类的析构函数在调用基类的构造函数
11.
虚函数和虚析构函数的意义
1.为什么基类的析构函数是虚函数?
在实现多态时,当用基类操作派生类,在析构时防止只析构基类而不析构派生类的状况发生。
(因为此时使用基类指针声明派生类对象,所以析构的时候,只析构基类的类型,而构造的派生类并不析构)
下面转自网络:源地址 http://blog.sina.com.cn/s/blog_7c773cc50100y9hz.html
a.第一段代码
复制代码
复制代码
#include<iostream>
using namespace std;
class ClxBase{
public:
ClxBase() {};
~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;};
void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};
class ClxDerived : public ClxBase{
public:
ClxDerived() {};
~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
};
int main(){
ClxDerived *p = new ClxDerived;
p->DoSomething();
delete p;
return 0;
}
复制代码
复制代码
运行结果:
Do something in class ClxDerived!
Output from the destructor of class ClxDerived!
Output from the destructor of class ClxBase!
这段代码中基类的析构函数不是虚函数,在main函数中用继承类的指针去操作继承类的成员,释放指针P的过程是:先释放继承类的资源,再释放基类资源.
b.第二段代码
复制代码
复制代码
#include<iostream>
using namespace std;
class ClxBase{
public:
ClxBase() {};
~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;};
void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};
class ClxDerived : public ClxBase{
public:
ClxDerived() {};
~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
void DoSomething() { cout << "Do something in class ClxDerived!" << endl; }
};
int main(){
ClxBase *p = new ClxDerived;
p->DoSomething();
delete p;
return 0;
}
复制代码
复制代码
输出结果:
Do something in class ClxBase!
Output from the destructor of class ClxBase!
这段代码中基类的析构函数同样不是虚函数,不同的是在main函数中用基类的指针去操作继承类的成员,释放指针P的过程是:只是释放了基类的资源,而没有调用继承类的析构函数.调用 dosomething()函数执行的也是基类定义的函数.
一般情况下,这样的删除只能够删除基类对象,而不能删除子类对象,形成了删除一半形象,造成内存泄漏.
在公有继承中,基类对派生类及其对象的操作,只能影响到那些从基类继承下来的成员.如果想要用基类对非继承成员进行操作,则要把基类的这个函数定义为虚函数.
********析构函数自然也应该如此:如果它想析构子类中的*重新定义或新的成员及对象*,当然也应该声明为虚的.
c.第三段代码:
复制代码
复制代码
#include<iostream>
using namespace std;
class ClxBase{
public:
ClxBase() {};
virtual ~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;};
virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};
class ClxDerived : public ClxBase{
public:
ClxDerived() {};
~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
};
int main(){
ClxBase *p = new ClxDerived;
p->DoSomething();
delete p;
return 0;
}
复制代码
复制代码
运行结果:
Do something in class ClxDerived!
Output from the destructor of class ClxDerived!
Output from the destructor of class ClxBase!
这段代码中基类的析构函数被定义为虚函数,在main函数中用基类的指针去操作继承类的成员,释放指针P的过程是:只是释放了继承类的资源,再调用基类的析构函数.调用dosomething()函数执行的也是继承类定义的函数.
如果不需要基类对派生类及对象进行操作,则不能定义虚函数,因为这样会增加内存开销.当类里面有定义虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间.所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数.
C++编程语言是一款应用广泛,支持多种程序设计的计算机编程语言。我们今天就会为大家详细介绍其中C++多态性的一些基本知识,以方便大家在学习过程中对此能够有一个充分的掌握。
多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。多态(polymorphism),字面意思多种形状。
C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。(这里我觉得要补充,重写的话可以有两种,直接重写成员函数和重写虚函数,只有重写了虚函数的才能算作是体现了C++多态性)而重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。编译器会根据这些函数的不同列表,将同名的函数的名称做修饰,从而生成一些不同名称的预处理函数,来实现同名函数调用时的重载问题。但这并没有体现多态性。
***************重载和多态有区别,前者是同时存在多种状态,应用时选择一个,后者是在应用时覆盖,屏蔽原有的**********
多态与非多态的实质区别就是函数地址是早绑定还是晚绑定。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的,就是说地址是早绑定的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定。
那么多态的作用是什么呢,封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。
最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。因为没有多态性,函数调用的地址将是一定的,而固定的地址将始终调用到同一个函数,这就无法实现一个接口,多种方法的目的了。
13。类内函数调用数据成员合法
14.对于等于运算符的重载,有些许注意要点
#include"iostream"
#include"cstdio"
using namespace std;
class math
{
public:
void show()
{
cout<<*p<<endl;
}
math()
{
p=new int(0);
cout<<"默认构造函数调用成功"<<endl;
}
math(int x)
{
p=new int;
*p=x;
cout<<"构造函数调用成功"<<endl;
}
~math()
{
delete p;
cout<<"析构函数调用成功"<<endl;
}
math& operator=(math k)
{
*p=*k.p;
return *this;
}
private:
int *p;
};
int main()
{
math x,y(1),z(4);
x.show();
x=y;y
x.show();
return 0;
}
如果使用引用接收引用返回值,则返回的引用必须具有较长的生存期,不可以引用局部变量。
如果使用引用接收值返回值,则引用了一个临时对象,该对象的生存期将延长到和这个引用相同
C:\Users\asuspc\AppData\Roaming\360se6\Application\360se.exe http://blog.csdn.net/duanruibupt/article/details/6881018 /////该网址成功解释了引用的区别
15.
C++的流插入运算符“<<”和流提取运算符“>>”是C++在类库中提供的,所有C++编译系统都在类库中提供输入流类istream和输出流类ostream。cin和cout分别是istream类和ostream类的对象。在类库提供的头文件中已经对“<<”和“>>”进行了重载,使之作为流插入运算符和流提取运算符,能用来输出和输入C++标准类型的数据。因此,凡是用“cout<<”和“cin>>”对标准类型数据进行输入输出的,都要用#include 把头文件包含到本程序文件中。
用户自己定义的类型的数据,是不能直接用“<<”和“>>”来输出和输入的。如果想用它们输出和输入自己声明的类型的数据,必须对它们重载。
对“<<”和“>>”重载的函数形式如下:
istream & operator >> (istream &, 自定义类 &);
ostream & operator << (ostream &, 自定义类 &);
即重载运算符“>>”的函数的第一个参数和函数的类型都必须是istream&类型,第二个参数是要进行输入操作的类。重载“<<”的函数的第一个参数和函数的类型都必须是ostream&类型,第二个参数是要进行输出操作的类。因此,只能将重载“>>”和“<<”的函数作为友元函数或普通的函数,而不能将它们定义为成员函数。
16.
请思考,return output的作用是什么?回答是能*连续向输出流插入信息*。output是ostream类的对象,它是实参cout的引用,也就是cout通过传送地址给output,使它们二者共享同一段存储单元,或者说output是cout的别名。因此,return output就是return cout,将输出流cout的现状返回,即保留输出流的现状。
还有一点要说明,在本程序中,在Complex类中定义了运算符“<<”重载函数为友元函数,因此只有在输出Complex类对象时才能使用重载的运算符,对其他类型的对象是无效的。如
cout<<time1; //time1是Time类对象,不能使用用于Complex类的重载运算符
17.
可以看到,在运算符重载中使用引用(reference)的重要性。利用引用作为函数的形参可以在调用函数的过程中不是用传递值的方式进行虚实结合,而是通过传址方式使形参成为实参的别名,因此不生成临时变量(实参的副本),减少了时间和空间的开销。此外,如果重载函数的返回值是对象的引用时,返回的不是常量,而是引用所代表的对象,它可以出现在赋值号的左侧而成为左值(left value),可以被赋值或参与其他操作(如保留cout流的当前值以便能连续使用“<<”输出)。但使用引用时要特别小心,因为修改了引用就等于修改了它所代表的对象。
18.
在进行类型转换的重载运算符的时候,我们不要在行参中用引用,因为一旦要对其他基本的数据类型转换为类的类型的时候,引用的对象有可能是基本的数据类型,但是引用代表的含义是对类进行取地址,但是此时数据类型不是类,就会报错
19.
多参数模板的返回值可能会出出现结果错误,但不是程序引起的,而是返回值的类型决定的
20.
模板必须是要有顺序的,是按顺序排列的
争取代码示例:
#include"iostream"
#include"cstdio"
using namespace std;
template<class T,int size>
void sort(T a[size])
{
cout<<"yes"<<endl;
}
int main()
{
int a[]={0,1,2,3,4,5,6,7,8,9};
sort<int ,10>(a); //核心
return 0;
}
错误代码示例:
#include"iostream"
#include"cstdio"
using namespace std;
template<class T,int size>
void sort(T a[size])
{
cout<<"yes"<<endl;
}
int main()
{
int a[]={0,1,2,3,4,5,6,7,8,9};
sort<10,int>(a); //核心
return 0;
}
应用引用的方式就可以不用显式的指定参数了
#include"iostream"
#include"cstdio"
using namespace std;
template<class T,int size>
void sort(T (&a)[size])
{
cout<<"yes"<<endl;
}
int main()
{
int a[]={0,1,2,3,4,5,6,7,8,9};
sort(a);
return 0;
}
21.
模板类型基本数据类型或者自己定义的类
22.
string类型变量在C++的#include"string"头文件里面
23.
用模板类实现栈
#include"iostream"
#include"cstdio"
using namespace std;
template<class T>
class stack
{
public:
stack()
{
top=0;
}
bool stackempty()
{
return top==0;
}
bool stackfull()
{
return top==99;
}
void stackpush(T a)
{
top++;
data[top]=a;
}
T stackpop()
{
top--;
return data[top+1];
}
private:
int top;
T data[100];
};
int main()
{
stack<int> int_stack;
stack<char> char_stack;
for(int i=1;i<=10;i++)
{
int_stack.stackpush(i);
}
while(!int_stack.stackempty())
{
cout<<int_stack.stackpop()<<' ';
}
cout<<endl;
char_stack.stackpush('A');
char_stack.stackpush('B');
char_stack.stackpush('C');
while(!char_stack.stackempty())
{
cout<<char_stack.stackpop()<<' ';
}
cout<<"End"<<endl;
return 0;
}
23
#include"iostream"
#include"cstdio"
using namespace std;
template<class T,size_t size>
class stack
{
public:
stack()
{
top=0;
}
bool stackempty()
{
return top==0;
}
bool stackfull()
{
return top==size;
}
T stackpop()
{
top--;
return data[top+1];
}
void stackpush(T a)
{
top++;
return data[top]=a;
}
private:
T data[size];
size_t top;
};
template<class T,size_t size,template<class U,size_t len> class stack>
class container
{
public:
void show()
{
cout<<kstack.stackpop()<<endl;
}
private:
stack<T,size> kstack;
};
int main()
{
container<int,10,stack> contain;
contain.show();
return 0;
}
以上是在container模板类中声明了一个固定模板参数表的私有成员的模板类成员,并用前面定义的stack来替换contanier中的模板参数类,实现对container的实例化
24.
组成的库来说提供了更好的代码重用机会。在C++标准中,STL被组织为下面的13个头文件:<algorithm>、<deque>、<functional>、<iterator>、<vector>、<list>、<map>、
<memory>、<numeric>、<queue>、<set>、<stack>和<utility>。
<algorithm>是所有STL头文件中最大的一个(尽管它很好理解),它是由一大堆模版函数组成的,可以认为每个函数在很大程度上都是独立的,其中常用到的功能范围涉及到比较、交换、查找、遍历操作、复制、修改、移除、反转、排序、合并等等。
<numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数,包括加法和乘法在序列上的一些操作。
<functional>中则定义了一些模板类,用以声明函数对象。
25.
50条忠告:(其中有几条觉得写的不够贴切,所以删了,发了余下的部分)
1.把C++当成一门新的语言学习;
2.看《Thinking In C++》,不要看《C++变成死相》;
3.看《The C++ Programming Language》和《Inside The C++ Object Model》,不要因为他们很难而我们自己是初学者所以就不看;
4.不要被VC、BCB、BC、MC、TC等词汇所迷惑——他们都是集成开发环境,而我们要学的是一门语言;
5.不要放过任何一个看上去很简单的小编程问题——他们往往并不那么简单,或者可以引伸出很多知识点;
6.会用Visual C++,并不说明你会C++;
7.学class并不难,template、STL、generic programming也不过如此——难的是长期坚持实践和不遗余力的博览群书;
8.如果不是天才的话,想学编程就不要想玩游戏——你以为你做到了,其实你的C++水平并没有和你通关的能力一起变高——其实可以时刻记住:学C++是为了编游戏的;
9.看Visual C++的书,是学不了C++语言的;
16.把时髦的技术挂在嘴边,还不如把过时的技术记在心里;
18.学习编程最好的方法之一就是阅读源代码;
19.在任何时刻都不要认为自己手中的书已经足够了;
20.请阅读《The Standard C++ Bible》(中文版:标准C++宝典),掌握C++标准;
21.看得懂的书,请仔细看;看不懂的书,请硬着头皮看;
22.别指望看第一遍书就能记住和掌握什么——请看第二遍、第三遍;
23.请看《Effective C++》和《More Effective C++》以及《Exceptional C++》;
24.不要停留在集成开发环境的摇篮上,要学会控制集成开发环境,还要学会用命令行方式处理程序;
25.和别人一起讨论有意义的C++知识点,而不是争吵XX行不行或者YY与ZZ哪个好;
26.请看《程序设计实践》,并严格的按照其要求去做;
27.不要因为C和C++中有一些语法和关键字看上去相同,就认为它们的意义和作用完全一样;
28.C++绝不是所谓的C的“扩充”——如果C++一开始就起名叫Z语言,你一定不会把C和Z语言联系得那么紧密;
29.请不要认为学过XX语言再改学C++会有什么问题——你只不过又在学一门全新的语言而已;
30.读完了《Inside The C++ Object Model》以后再来认定自己是不是已经学会了C++;
31.学习编程的秘诀是:编程,编程,再编程;
32.请留意下列书籍:《C++面向对象高效编程(C++ Effective Object-Oriented Software Construction)》《面向对象软件构造(Object-Oriented Software Construction)》《设计模式(Design Patterns)》《The Art of Computer Programming》;
34.请把书上的程序例子亲手输入到电脑上实践,即使配套光盘中有源代码;
35.把在书中看到的有意义的例子扩充;
36.请重视C++中的异常处理技术,并将其切实的运用到自己的程序中;
37.经常回顾自己以前写过的程序,并尝试重写,把自己学到的新知识运用进去;
38.不要漏掉书中任何一个练习题——请全部做完并记录下解题思路;
39.C++语言和C++的集成开发环境要同时学习和掌握;
40.既然决定了学C++,就请坚持学下去,因为学习程序设计语言的目的是掌握程序设计技术,而程序设计技术是跨语言的;
41.就让C++语言的各种平台和开发环境去激烈的竞争吧,我们要以学习C++语言本身为主;
42.当你写C++程序写到一半却发现自己用的方法很拙劣时,请不要马上停手;请尽快将余下的部分粗略的完成以保证这个设计的完整性,然后分析自己的错误并重新设计和编写(参见43);
43.别心急,设计C++的class确实不容易;自己程序中的class和自己的class设计水平是在不断的编程实践中完善和发展的;
44.决不要因为程序“很小”就不遵循某些你不熟练的规则——好习惯是培养出来的,而不是一次记住的;
45.每学到一个C++难点的时候,尝试着对别人讲解这个知识点并让他理解——你能讲清楚才说明你真的理解了;
46.记录下在和别人交流时发现的自己忽视或不理解的知识点;
47.请不断的对自己写的程序提出更高的要求,哪怕你的程序版本号会变成Version 100.XX;
48.保存好你写过的所有的程序——那是你最好的积累之一;
49.请不要做浮躁的人;
50.请热爱C++!
第四部分:
C++头文件一览
C、传统 C++
#include <assert.h> 设定插入点
#include <ctype.h> 字符处理
#include <errno.h> 定义错误码
#include <float.h> 浮点数处理
#include <fstream.h> 文件输入/输出
#include <iomanip.h> 参数化输入/输出
#include <iostream.h> 数据流输入/输出
#include <limits.h> 定义各种数据类型最值常量
#include <locale.h> 定义本地化函数
#include <math.h> 定义数学函数
#include <stdio.h> 定义输入/输出函数
#include <stdlib.h> 定义杂项函数及内存分配函数
#include <string.h> 字符串处理
#include <strstrea.h> 基于数组的输入/输出
#include <time.h> 定义关于时间的函数
#include <wchar.h> 宽字符处理及输入/输出
#include <wctype.h> 宽字符分类
标准 C++
#include <algorithm> 通用算法
#include <bitset> 位集容器
#include <cctype>
#include <cerrno>
#include <clocale>
#include <cmath>
#include <complex> 复数类
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque> 双端队列容器
#include <exception> 异常处理类
#include <fstream>
#include <functional> 定义运算函数(代替运算符)
#include <limits>
#include <list> 线性列表容器
#include <map> 映射容器
#include <iomanip>
#include <ios> 基本输入/输出支持
#include <iosfwd> 输入/输出系统使用的前置声明
#include <iostream>
#include <istream> 基本输入流
#include <ostream> 基本输出流
#include <queue> 队列容器
#include <set> 集合容器
#include <sstream> 基于字符串的流
#include <stack> 堆栈容器
#include <stdexcept> 标准异常类
#include <streambuf> 底层输入/输出支持
#include <string> 字符串类
#include <utility> 通用模板类
#include <vector> 动态数组容器
#include <cwchar>
#include <cwctype>
C99 增加
#include <complex.h> 复数处理
#include <fenv.h> 浮点环境
#include <inttypes.h> 整数格式转换
#include <stdbool.h> 布尔环境
#include <stdint.h> 整型环境
#include <tgmath.h> 通用类型数学宏
25.流式输入输出常用的函数
cout.put()将单个字符插入到缓存中,可以实现连续的插入
#include"iostream"
#include"cstdio"
using namespace std;
int main()
{
cout.put(65).put(78).put(45)<<endl;
return 0;
}
cout.write(字符串首地址,输出长度)将字符串整体输出或者输出字符串的部分内容
#include"iostream"
#include"cstdio"
using namespace std;
int main()
{
char a[]="lantian";
cout.write(a,7)<<endl;
cout.write(a+1,6);
return 0;
}
26.C++缓存和流式输入输出
实际上,在内存中为每一个数据流开辟一个内存缓冲区,用来存放流中的数据。当用cout和插入运算符“<<”向显示器输出数据时,先将这些数据送到程序中的输出缓冲区保存,直到缓冲区满了或遇到endl,就将缓冲区中的全部数据送到显示器显示出来。在输入时,从键盘输入的数据先放在键盘缓冲区中,当按回车键时,键盘缓冲区中的数据输入到程序中的输入缓冲区,形成cin流,然后用提取运算符“>>”从输入缓冲区中提取数据送给程序中的有关变量。总之,流是与内存缓冲区相对应的,或者说,缓冲区中的数据就是流。
27.文件读取和写入实例
#include"iostream"
#include"cstdio"
#include"cstdlib"
#include"fstream"
using namespace std;
struct node
{
char name[20];
char sex[10];
int age;
};
typedef struct node p;
p student[3]={"lantian","man",19,"baba","man",45,"mama","woman",46};
p stud;
ostream& operator<<(ostream& out,p stu)
{
out<<stu.name<<endl;
out<<stu.sex<<endl;
out<<stu.age<<endl;
return out;
}
int main()
{
ofstream outfile("student.txt",ios::trunc);
for(int i=0;i<=2;i++)
{
outfile.write((char*)&student[i],sizeof(student[i]));
}
outfile.close();
ifstream infile("student.txt",ios::in);
for(int i=0;i<=2;i++)
{
infile.read((char*)&stud,sizeof(stud));
cout<<stud<<endl;
}
infile.close();
return 0;
}
27.对于二进制文件的重要应用函数,指针转移函数
输入:seekg(偏移量,正代表向右,负代表向左,ios::beg(指针移到起点)//ios::cur(指针在原处)//ios::end(指针在终点))
tellg()
输出:seekp()同上
tellp()
28.文件读取综合实例
#include"iostream"
#include"cstdio"
#include"fstream"
#include"cstdlib"
using namespace std;
struct node
{
char name[20];
char sex[10];
int age;
};
typedef struct node point;
point student[3]={"lantian","man",19,"mama","woman",46,"baba","man",45};
point stud[2];
point k;
ostream& operator<<(ostream& out,point k)
{
cout<<k.name<<endl;
cout<<k.sex<<endl;
cout<<k.age<<endl;
return out;
}
int main()
{
fstream iofile("student.txt",ios::in|ios::out|ios::trunc|ios::binary);
if(!iofile)
{
cerr<<"error!"<<endl;
exit(0);
}
for(int i=0;i<=2;i++)
{
iofile.write((char*)&student[i],sizeof(student[i]));
}
for(int i=0;i<=2;i++)
{
iofile.seekg(0,ios::beg);
iofile.read((char*)&k,sizeof(k));
cout<<k<<endl;
}
cout<<"***********************"<<endl;
iofile.seekg(0,ios::beg);
for(int i=0,t=0;i<=2;i=i+2,t++)
{
iofile.seekg(i*sizeof(k),ios::beg);
iofile.read((char*)&stud[t],sizeof(k));
cout<<stud[t]<<endl;
cout<<"begin to change!"<<endl;
cin>>stud[t].name>>stud[t].sex>>stud[t].age;
iofile.seekp(i*sizeof(k),ios::beg);
iofile.write((char*)&stud[t],sizeof(k));
}
iofile.seekg(0,ios::beg);
for(int i=0;i<=2;i++)
{
iofile.read((char*)&k,sizeof(k));
cout<<k<<endl;
}
iofile.close();
return 0;
}
27.流式输入
ch=cin.get()从输入流中获取一个字符,返回值是读入的字符,文件结束返回EOF
cin.get(char (&(可有可无))ch)将从流中提取的字符赋值给指定的字符变量
#include"iostream"
#include"cstdio"
using namespace std;
int main()
{
char a[20];
cin.get(a,5,'x'); //读取5-1个字符,x为结束标志,如果扫描到x提前读入结束
cout<<a<<endl;
return 0;
}
cin.getline(char *buf,int limit,deline='\n'(默认的结束符)) 与上面的类似,读取一行的内容
cin.read(char *buf,int size) //向指定的内容中输入固定数目的内容
cin.eof()文件判空
cin.peek()检查该字符的下一个字符,但是注意指针并没有移动,只是检查下一个字符,指针仍停留在当前字符处
cin.putback(ch)将字符ch重新插入到缓冲区的指针位置
cin.ignore(int n=1,char ch=EOF) 等号是默认字符和值的意思,在没有函数的参数的时候,应用默认值,否则应用函数的参数的值,代表跳过n个字符或者知道遇到ch字符的时候,提起包括ch字符都跳过(此跳过值得是指针的移动)
28.
try-catch语句的调用规则,如果在同一函数中的话,直接找本函数,如果本函数没,返回他的调用层继续找,知道最后还没有找到的话,返回程序报错异常处理机制
29.析构函数和异常处理
#include"iostream"
#include"cstdio"
using namespace std;
class aa
{
public:
aa()
{
a=0;
cout<<"construct"<<endl;
}
~aa()
{
cout<<"destruct"<<endl;
}
void setdata(int k)
{
if(k>=100) throw k;
a=k;
cout<<"change successfully"<<endl;
}
private:
int a;
};
int main()
{
aa wa;
try
{
wa.setdata(111);
}
catch(int d)
{
cout<<"error!"<<endl;
cout<<"throw:"<<d<<endl;
}
return 0;
}
#include"iostream"
#include"cstdio"
using namespace std;
class aa
{
public:
aa()
{
a=0;
cout<<"construct"<<endl;
}
~aa()
{
cout<<"destruct"<<endl;
}
void setdata(int k)
{
if(k>=100) throw k;
a=k;
cout<<"change successfully"<<endl;
}
private:
int a;
};
int main()
{
try
{
aa wa; //如果类对象是在try语句块中进行定义的,那么在出现异常情况的时候,立即离开try语句块(如果是函数且对象在try内定义,那么立即进入catch语句块进行一场处理)
wa.setdata(111);
}
catch(int d)
{
cout<<"error!"<<endl;
cout<<"throw:"<<d<<endl;
}
return 0;
}
简而言之,只要try语句块中出现了对对象的声明的话,只要出现了一场(只要开始throw)必须要调用先调用析构函数然后再转入catch语句块中进行一场处理
30.
在符合继承关系中要小心这么一种情况
base1
base2 base3
base4
base2,3分别从base1中继承,而base4从base2和base3中继承,这时候在base4的构建时
我们先调用base1的构造函数在调用base2,3的构造函数,最后调用base4的构造函数,所以如果base4中没有对base1进行初始化操作,那么会导致base1先调用默认的构造函数,在调用base2,base3的构造函数,但是base2,3中的构造函数会先调用base1的构造函数,这时候和之前就会出现矛盾,所以我们规定,在这种情况的时候base4中也要写出base1的构造函数的调用语句
31.
私有成员
1.在基类的作用域中私有始终是私有,其他的类型变量也都不会改变属性
2.在派生类的作用域中基类的私有是不可访问
3.但是,我们可以通过基类的公有函数作为接口来间接的调用所谓的不可访问变量
再者,对于私有继承
虽然基类的共有函数变成派生类中的私有函数,但是派生类中的共有函数可以调用私有成员,并且在基类的作用域中,共有函数还是公有,还是可以调用私有成员变量的,所以,这种情况下我门还是可以以基类的共有函数做接口调用到在派生类中视为不可访问变量的私有成员