这里写的主要是一些c/c++值得注意的地方和c++primer笔记,方便以后回顾,复习c++,当然会有一些错误,发现后再改正
//当形参引用时,数组不能转化为指针 宽字符想要输出对应的字符,应用wcout。
//“\”是连接符,当宏定义用多行时常用
1:c中不可以连续赋值;
c++可以,如int a,b,c;a=b=c=1;
2:一般int main(){
...
}
如果main函数里不加system("pause");的话,程序运行后立马闪退,为了让程序暂停,也可以加
cin.get();(c++中,)c中可以加getchar();(输入后需按回车键才能发送到输出流),getche();(不 用按回车键,但输入的字符不会回显到屏幕上 ),getch();(不用按回车键,回显)
为什么呢?就已这个例子来说吧
int main(){
cout << "How many carrots do you have?" << endl;
int n=0;
cin >> n;
cout << "Here are two more;"<
cin.get();
cin.get();
//system("pause");
return 0;
}
当我们根据提示输入一个数字,比如12,然后按回车,其实输入缓冲区里可能还有数据,按回车 后,第一个用来接收缓冲区里的数据,第二个用来停顿(当然用system("pause");最好)
3: c++在c的基础上又增加了long long型,sizeof(long long)=64;(一般来说);
bool型,值为false或true
4: 可以通过包含头文件climits来查看整型的最大值最小值(climits专门用于检测整型数据数据类型 的表达值范围。)
int main(){
int n_int = INT_MAX; //int型的最大值,下面依次类推
short n_short= SHRT_MAX; //这里写法注意,是SHRT_MAX;
long n_long = LONG_MAX;
long long n_llong = LLONG_MAX;
cout << "int is " << sizeof(int) << " bites" << endl;
cout << "short is " << sizeof(short) << " bites" << endl;
cout << "long is " << sizeof(long) << " bites" << endl;
cout << "long long is " << sizeof(long long) << " bites" << endl << endl;
cout << "Maximum values:" << endl;
cout << "shor:" << n_short << endl;
cout << "int:" << n_int << endl;
cout << "long" << n_long << endl;
cout << "long long:" << n_llong << endl;
cout << "The minumum int value:" << INT_MIN << endl;
cout << "Bits per byte:" <
return 0;
}
5: c++可以这样初始化:int a(10);//a=10;
int b{10};//b=10;
int c={};//c=0
int d{}; //;也可以不使用=
为什么要用大括号呢?因为大括号初始化可以初始化任何类型的变量(可以加等号,也可以不加)
6: 如果知道变量的值大于16位整数的最大可能值时,则用long,即使系统上int为32位(即sizeof (int=4)),也应该这么做,这样,
将程序移植到16位系统时,就不会突然无法正常工作。如果存储的值超过20亿,可使用long long.
7: 可以这样输出八进制,十进制,十六进制
cout<
8: 转义字符\a表示振铃字符(ASCII码为7),可以使终端扬声器振铃
9:float a = 2.34e+22f;
float b = a + 1.0f;
cout << "a=" << a << endl;
cout << "b-a=" << b - a << endl;
b-a应该为1,可是运行程序时输出的确是
a=2.34e+022;
b-a=0;
问题在于,2.34+22是一个小数点左边有23位的小数,加上1就是在第23位加1,但float类型只能表示 数字中的前6位或前7位,因此修改第23位对这个值不会有任何影响。
11: c中的宽字符基于wchar_t数据类它在几个头文件(包括wchar.h)中都有定义,像这样:
typedef unsigned short wchar_t;
因此,wchar_t数据类型与无符号短整型相同,都是16位宽
定义一个宽字节可以这样:
wchar_t c=‘A’;直接输出的话cout<
//cout<
也可以直接定义宽字符串wchar_t c[]=L"hello"; // sizeof(c)是12(5个字符,1个字符串结束标志,每 个占2位)
但这样直接输出cout<
wchar_t是用两个字节存储的,例如字符串(wchar_t )“hello!”,intel将其在 内存中存储为
48 00 65 00 6c 00 6f 00 21 00
wchar_t *p=L"hello";
printf("%d\n",strlen(p));
所以如果这样的话 ,结果是1;因为strlen函数获取字符串的第一个字符后就遇到了0,即结束标志
,而单字节就不会有这种情况,为了解决这个问题,有宽字节的strlen版本,就是wcslen(wide
character string length)
12:c++中,求字符串长度不是c中的strlen();而是string.size();或string.length;
string a="hello";
cout<
13 :##表示粘贴符号
比如# define T(x) L##x
如果定义T("hello"),就相当于L"hello";
14:c++中的强制类型转换
typename(values)//更像是函数调用
c中: (typename)value
c++还引用了4个强制类型转换运算符,对它们的使用要求更为严格,例如:
static_cast
===================================================================================
c++中,如果两个类型有关联,比如int型变量和float型变量可以相互转换,举个例子:
int i=3.14+1;//编译器会警告可能会丢失数据,但不会报错
相加的两个类型不同,c++不会直接将两个不同类型的值相加,而是先根据类型转换规则将类型统一后再求值,这些是自动进行的,不用程序员操心,有时甚至不用程序员了解,被称作"隐式转换";
其他类型的隐式转换:
1):数组转换为指针:
int arr[5];
int *p=arr;//arr转换为指向数组首元素的指针
当数组被用作decltype关键字的参数,或者作为取地址符(&)、sizeof
以及typeid等运算符的运算对象时,上述转换不会发生,同样的,如果
用一个引用来初始化数组,上述转换也不会发生,当在表达式中使用函
数类型时会发生类似的指针转换
2)指针的转换
c++还规定了几种其他的指针转换方式,包括常量整数0或者字面
值,nulllptr能转换成任意指针类型;指向任意非常量的指针能转换成
void *;指向任意对象的指针能转换成const void *;
3)转换成布尔类型;
存在一种从算术类型或指针类型向布尔类型自动转换的机制
4)转换成常量:
允许将指向非常量类型的指针转换成指向相应的常量类型的指针,
对于引用也是这样,比如:
int i;
const int &j=i; //可以将非常量转换成常量的引用
const int *p=&i; //可以将非常量的地址转换成const的地址
int &q=j,*r=p; //反之则错误
5)类类型定义的转换:
类类型能定义由编译器自动执行的转换,不过编译器每次只能执行一
种类类型的转换;
string s,t="a string";//字符串字面值转换为string 类型
while(cin>>s) //cin转换为布尔值
显示转换
1)命名的强制类型转换
cast-name
type是转换的目标类型而expression是要转换的值,如果type是引用
类型,则结果是左值,cast-name是static_cast,dynamic_cast,
const_cast和reinterpret_cast(reinterpret)中的一种,
a.dynamic_cast:
dynamic_cast支持运行时类型识别,cast-name指定了执行的是哪
种转换
b.static_cast:
当需要把一个较大的算术类型赋给较小的类型时,static_cast
非常有用,此时不会警告丢失数据了;
static_cast对于编译器无法自动执行的类型转换也非常有用,
例如,可以使用static_cast找回存在于void *指针中的值
void *p=*d;//任何非常量对象的地址都能存入void *
double *dp=static_cast
//将void *换回初始的指针的类型
当把指针存放在void *中,并且使用static_cast将其强制转
换回原来的类型时,应该保证指针的值保持不变,因此,必须确
保转换后所得类型就是指针所指的类型,类型一旦不符,将产生
未定义的后果
c.const_cast:
const_cast只能改变运算对象的底层const,
const char *pc;//pc是指针,指向const char类型,这里
//的const是底层const,比如char *const pc,这个const就是
//顶层const,个人觉得没必要刻意去记,这些东西很好理解
char *p=const_cast
//指对象的值
d.reinterpret_cast
reinterpret_cast通常为运算符对象的位模式提供较低层
次上的重新解释,比如:
int *ip;
char *pc=reinterpret_cast
请牢记pc所指的真实对象是一个int而非char,如果把pc当
成普通的字符指使用就可能在运行时发生错误,例如:
string str(pc);//这可能导致异常的运行时行为
使用reinterpret_cast是非常危险的,上面这个例子很
好的证明了这一点,其中的关键问题是类型改变了,但编译
器没有给出任何警告或错误的提示信息,当用一个int的地址
初始化pc时,由于显示地声称这种转换合法,所以编译器不
会发出任何警告或错误信息,接下来再使用pc时就会认定它
的值是char *类型,编译器没法知道它实际是指向int,这种
错误很难发现
2)旧式的强制类型转换
早期的c++中,显示地进行强制类型转换有两种形式:
type (expr); //函数形式
(type) expr; //c语言形式
===================================================================================
15: c++11新增了一个工具,让编译器能够根据初始值的类型推断变量的类型,为此,它重新定义了auto,auto是c语言的一个关键字,但很少使用
auto x=2.1;//x是double型
16:cin可以读取一个字符串到输入流,但是如果用cin输入时,输入的字符串含有空格键,tab 键,回车键等,就会结束输入,这是面向单词的输入,
istream中的类(如cin)提供了一些面向行的类成员函数:(比如定义了一个数组name[size])getline(name,size)和get(name,size),区别是getline()输入结束后就会丢弃换行符,而get()不会(即缓冲区还有换行符)
17:char a[] ="string";
char *p = a;
cout << "a=" << a << " &a= " << &a << endl;
cout << "p=" << p << " &p=" << &p << " (int *)p=" << (int *)p << endl;
结果a=string &a=0018F7c0
p=string &p=0018F7B4 (int *)p=0018F7c0
18: 通常,cout在显示bool值之前将其转化为int,但cout.setf(ios:boolalpha)(老式c++)和cout.setf(
ios_base::boolalpha)(c++11)函数调用设置了一个标记,命令cout显示true和false,而不是1和0
int a;
cout << (a>3) << endl;
cout.setf(ios_base::boolalpha);
cout << (a>3)<
19:对于i++和++i,如for(int i=0;i
i++是先复制一个副本,将其加1,然后再将副本返回
20: 一个细节,值得注意
int i = 8;
if (i++==9||i==9)
cout << "yes" << endl;
else cout << "no" << endl;
c++规定,||是个顺序点,也就是说,先修改左侧的值,再对右侧进行判定(c++11的说法是,运算符左边
的子表达式先于右边的子表达式)冒号和逗号运算符也是顺序点
左边i++==9 这里i还是8,然后到右边,i就是9了,所以if里面为true
21:c++从c继承了一个与字符相关的,非常方便的函数软件包,它可以简化诸如确定字符是否为大写字母,数字
标点符号等工作,这些都在cctype(c中是ctype.h)中定义的,ep:ch是一个字母,则isalpha(ch)函数返回
一个非零值,否则返回0;同样,如果ch是标点符号(如逗号或句号),函数ispunct(ch)函数返回true、(
这些函数的返回类型为int,而不是bool,但通常bool转换竜让您能够将它们视为bool类型)
使用这些函数就方便多了,例如:
if(ch>='a'&&ch<='z'||ch>'A'&&ch<'Z')
可以用if(isalpha(ch))代替
还有很多函数,请看C.Primer.Plus (197页)
22:for循环可以这样用
int a[5] = {1,3,5,7,9};
for (int i : a)
cout << i << endl;
结果输出数组a里的全部元素
23:通常,在输入字符,判断何时结束时一般都是按#键结束(或者
其他的一些按键) ,这样虽然大多数情况下不会有问题,但是
如果判断结束的字符是我们要输入的呢?这不就输入不了吗?如果输
入是来自文件·,则可以使用检测文件尾(EOF)来判断是否结
束输入,不过c++还可以用键盘模拟文件尾,过程是这样的,检测到
EOF后,cin将两位(eofbit和failbit)都设置为1。可以通过成员函
数eof()看eofbit是否被设置,如果检测到EOF,cin.eof()返回ture;
同样,用cin.fail()的话,返回true;一般使用较多的是fail(),因为f
ail()可以用于更多的实现
# include
using namespace std;
int main()
{ char ch;
int count=0;
cin.get(ch); //cin不会识别空格,tab,换行等空白键
while(cin.fail()==false)//当没有检测到EOF,循环
{
count++;
cout<
}
cout<
system("pause");
return 0;
}
24:int *p=new int[size];
delete []p;
25:double(*p)(double a, double b);
p = max;//函数名就是函数地址
double a = p(4, 5);//函数名是函数地址,指针p指向函数的地址,所以p应该和max有相同的
double b = (*p)(4, 5);//p是函数指针,*p就是函数
cout << "a=" << a << endl << "b=" << b << endl;
结果a和b一样,这两种用法都可以
如果声明了一个函数,又定义一个指向该函数的指针,可以使用c++11的·自动类型推断功能,这样
就方便得多,上面的就可以写成
auto p=max;而不用写成
double(*p)(double a, double b);
p = max;
了,但是auto只能用于单值初始化,而不能初始化列表--
还可以用typedef简化
typedef double(*type)(double a, double b);
type p;
26: 内联函数(inline)
内联函数相比普通函数,调用时相当于直接使用函数的副本,而不用去找到函数的地址执行函数,所以
速度相对稍快点,不过计算机运行速度非常快,所以也快不了多少,而且内联函数更占用内存
声明和定义时必须都加上inline关键字,而且内联函数不能递归,关于内联,了解即可,一般用的比较少
c中使用宏-----内联代码的原始实现
# define SQUARE(X) X*X
这是通过字符串替换实现的,而不是传递参数
SQRARE(5.0); //可以实现
SQUARE(1+2); //得到的不是9,而是1+2*1+2
SQUARE(a++); //得到的是a++*a++;a会自增两次
这里不是为了说如何使用宏,而是为了说明当用c的宏实现某些功能时,应转化为内联
27: ofstream继承了ostream,ostream中的一些方法:
1)setf()
让你能够设置各种格式化状态,例如,方法调用setf(ios_base::fixed);将对象置于使用定点表 示法的模式,setf(ios_base::showpoint);将置于显示小数点的模式,即使小数部分为0
2)precision()
指定显示多少位小数(假定对象处于定点模式下)。
所有这些设置都将一直保持不变,知道再次调用相应的方法重新设置它们。
3)width()设置下一次输出操作使用的字段宽度,这种设置只在显示下一个值时有效,然后将恢复到默认设置,默认的字段宽度为0
函数file_it使用了两个调用
ios_base::fmtflags initial;
initial=ios.setf(ios_base::fixed);//save initial formatting state
...
iso.setf(initial); //restore initial formatting state
方法setf()返回调用它之前有效的所有格式化设置,ios_base::fmtflags是存储这种信息所需的数据类型
名称,因此,将返回值赋给initial将存储调用file_it()之前的格式化设置,然后便可以使用变量initial
作为参数来调用setf(),将所有的格式化设置恢复到原来的值。
28:默认参数(声明时给默认值,定义时不要加,有些编译器会报错)
例如函数声明void fun(int n=1);
如果调用函数时忘了加参数,则默认传入的参数是1
对于带参数列表的函数,必须从右向左添加默认值,例如
int fun(int n,int m=2,int j=3); //可以
int fun(int n,int m=2,int j); //不可以
实参按从左到右的顺序依次赋给相应的实参,而不能跳过任何参数
int n=fun(1,,4);//不可以
29:函数重载
当调用函数时,如果未找到参数列表匹配的函数,c++会尝试使用标准类型转换强制进行匹配,如果 可以唯一匹配则匹配,否则若有多个可以
匹配,将视为错误
匹配函数时,并不区分const和非const变量
但是将非const赋给const是合法的,反之则不行
函数重载,不可以返回值类型不同,参数相同,但可以返回值类型,参数都不同
函数重载不要滥用,仅当函数基本执行相同的任务,但是用不同形式的数据时,才采用函数重载,
30: cv-限定符:
1)const
2)volatile (不稳定的;挥发性的;爆炸的)
31: 名称空间
1)术语:
a: 声明区域(declaration region)
b: 潜在作用域(potential scope)
2)名称空间
用namespace创建名称空间
ex:
namespace Jack{
double pail;
void fetch();
int pal;
struct Well{...};
}
namespaace Jill{
double bucket(double n){...};
double fetch;
int pal;
struct Hill{...};
}
名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中;名称空间是开放的,
可以把名称加入到已有名称空间中,例如:
namespace Jack{
char *goose(const char *);
}
同样,若名称空间中为某函数提供了原型,可以再次使用该名称空间来提供该函数的代码
namespace Jack{
char *goose(const char *)
{...}
}
如果使用using namespace Jack;
局部变量会隐藏Jack里同名的变量,
31:初始化string对象的方式
string s1; s1为空串
string s2("abc"); 用字符串字面值初始化s2
string s3(s2); 将s3初始化为s2的一个副本
string s4(n,'c'); 将s4初始化为字符'c'的n个副本
string的常用操作:
s.empty() 若为空串,返回true,否则返回false
s.size() 返回s中字符的个数
但是注意!!!!!!!!
string s="ds"+"df"; 这样是非法的,不允许
32: class内部的函数优先是使用内联函数的,即使我们自己没写inline,不过要是无法用内联函数就不会用内
联函数编译了
c++远征之封装篇
33: 内存分区
栈区:int x=0;int *p=NULL;
堆区:int *p=new int;
全局区:存储全局变量及静态变量
常量区: string str="hello";
代码区:存储逻辑代码的二进制
34: 初始化列表
可以通过初始化列表的方式
class Student
{
public:
Student():m_strName("Jim"),m_iAge(19) //对于多个数据成员初始化,要用逗号隔开,赋值时
//用括号
{
...
}
private:
string m_strName;
int m_iAge;
};
初始化列表优先于构造函数执行,即先给数据成员赋值,在调用构造函数,初始化列表只能用于构造函 数,这种方式初始化速度快,效率高,推荐使用这种方式,那么,既然构造函数完全可以做这种工作,为
什么又要初始化列表呢?仅仅是为了效率高,速度快?不是的,举个例子:
class Circle
{
public:
Circle(){m_dPi=3.14;} //如果用这种方式初始化,编译器会报错
Circle():m_dPi(3.14){} //但是初始化列表可以
private:
const double m_dPi;
};
圆这个类中,Pi是不变的,因此用const,以后建议这样用
Student(string name,int age):m_strName(name),m_iAge(age)
{
...
}
35: 拷贝构造函数
class Student
{
public:
Student(string name,int age):m_strName(name),m_iAge(age)
{
cout<<" Student(string name,int age):m_strName(name),m_iAge(age)"<
private:
string m_strName;
int m_iAge;
};
当定义了一个Student类,然后再main函数中:
Student s1;
Student s2=s1;
Student s3(s1);
这样实例化s1调用的是我们在类中定义构造函数,而s1和s3调用的是拷贝构造函数,因为自己没定义拷贝
构造函数,所以只会打印一行"Student(string name,int age):m_strName(name),m_iAge(age)",而不是三行;如何定义拷贝函数呢?以这个Student为例:
定义格式:类名(const 类名 &变量名){ }
Student(const Student &s)
{
cout<<"Student(const Student &s)"<
当要传递类实例化的对象的值时就会调用拷贝构造函数,比如定义一个函数void test(Student s){}
这时由于是值传递,所以会调用拷贝构造函数
36: 析构函数
定义格式:~类名(){ } //不加任何参数
37: 对象成员
即类的成员还是类,比如定义一个线段类,两点确定一条线段,所以线段应该有两个表示点的数据成员, 而点也是一个类
38: 深拷贝与浅拷贝
浅拷贝:只是简单赋值,比如:
class Array
{
public:
Array(){m_iCount=5;m_pArr=new int[m_iCount];}
Array(const Array &arr)
{
m_iCount=arr.m_iCount;
m_pArr=arr.m_ipArr;
}
private:
int m_iCount;
int *m_pArr;
};
main()中:
Array arr1;
Array arr2=arr1;
这样浅拷贝后,arr1和arr2的成员m_pArr都指向同一块内存,这样显然不是我们的意图
深拷贝:当不是进行简单的值的拷贝,而是将堆中内存的数据也进行拷贝
上述代码中拷贝构造函数应改为:
Array(const Array &arr)
{
m_iCount=arr.m_iCount;
m_pArr=new int[m_iCount];
for(int i=0;i
}
39: 对象指针
其实说不说无所谓,没什么好说的,比如Coordinate类
Coordinate *p=new Coordinate; //不过这里用new会自动调用Coordinate的构造函数,而malloc则只是
//分配一块内存
p->m_iX=1; //(*p).m_iX=1;
p->m_iY=2; //(*p).m_iY=2;
delete p;
p=NULL;
40:this指针(就是指向自身数据的指针)
比如Array这个类
class Array{
public:
Arrray(int len){m_iLen=ien;}
private:
int m_iLen;
};
如何知道是将参数赋值给数据成员还是把数据成员赋值给参数呢?其实是通过this指针赋值的,虽然我们
没有写this,但编译器会自动加上this,这样就算参数与数据成员同名也不会有问题
class Array{
public:
Arrray(int len){this->len=len;}
private:
int this->len;
};
还有一种情况,因为类的代码只有一份,在代码区,即对象的数据是私有的,而成员函数(在代码区)是 共享的,当实例化多个对象时,如何分辨是哪个对象的调用函数呢?这也要用到this指针
class Array{
public:
Arrray(Array *this,int len){this->len=len;}
private:
int len;
};
Array arr1(1); ->Array arr1(this,1); ->this->len=len;
Array arr2(2); ->Array arr2(this,2); ->this->len=len;
Array arr3(3); ->Array arr3(this,3); ->this->len=len;
41: 常对象成员和常成员函数
常对象成员:比如定义一个坐标类Coordinate,再定义一个线段类Line,当实例化一个线段类后,不希望
它的值被修改,可以用const修饰,
class Line{
public:
Line(int x1,int y1,int x2,int y2):m_coorA(x1,y1),m_coorB(x2,y2)
{
...
}
private:
const Coordinate m_coorA; //常对象成员
const Coordinate m_coorB;
};
常成员函数:
比如Coordinate类
class Coordinate{
public:
Coordinate(int x,int y);//这里写的是声明,节省时间
void changeX(int x)const;
void changeY(int y)const;
private:
int m_iX;成员的值
int m_iY;
};
对于常成员函数,不能改变数据成员的值,因为实际上是这样的:
void changeX(const Coordinate *this,int x)
{
this->m_iX=x;
}
void changeX(int x)const; //这两个函数也是互为重载的
void changeX(int x);
那么什么时候用常成员函数呢?如果定义了常成员函数,要使用的话,在前面加上const,即:
const Coordinate coor(1,2);//这时coor是常对象
coor.changeX(4); //这里就是调用常成员函数了,即常对象调用常成员函数
42:常指针和常引用
以Coordinate类为例
class Coordinate{
public:
Coordinate(int x,int y)
{
m_iX=x;
m_iY=y;
}
int getX()
{
return m_iX;
}
int getY()
{
return m_iY;
}
void printinfo()const
{
cout<<"("<
private:
int m_iX;
int m_iY;
};
int main()
{
Coordinate coor1(3,5);
const Coordinate &coor2=coor1;//常引用
const Coordinate *p=&coor1; //指向常对象的指针
coor1.printinfo(); //正确
coor2.getX(); //不可以,因为getX不是常成员函数
p->getX(); // 不可以,
Coordinate * const p1=&coor; //这里const修饰的是p1,表示p1是一个指向对象的常指针
p1->getX(); //可以的
p1=p; //不可以
return 0;
}
43:vector操作和string差不多,不过
vector
向vector中增加元素只能用成员函数push_back();索引只能访问其元素
最好用string::size_type接收string的size函数的返回值和索引
initializer_list和vectot不同的是,赋值时:
initializer_ilst
initializer_list
结果是a,b共享元素,而且initializer_list对象的元素都是常量
c++远征之继承篇
1:比如定义了人类和工人类
class Person{ class Worker{
public: public:
void eat(); void eat();
void Work(); //特有
public: private:
string m_sreName; string m_sreName;
int m_iAge; int m_iAge;
}; int m_iSalary; //特有
};
如果这样写,代码量会大大增加;所以就有了继承
因为工人类是人类,所以可以继承:
class Worker:public Person
{
public:
void Work(); //特有
private:
int m_iSalary;//特有
};
这样代码就大大简化了,这样实例化一个Worker对象后,访问的就都是Worker的数据成员和成员函数了
在不涉及继承的时候,protected和private是一样的;注意构派生类造函数用初始化列表赋值时:
Worker(string name="lanbo", int age=19, int salary=20000):Person(name,age),m_iSalary(salary)
{....
}
Has a关系:是一种包含关系
Is a 派生类继承了基类,就可以说派生了是一个基类,即Is a;比如工人类继承了人类,那么就是工人
也是人,这种关系就是Is a,所以可以用子类的对象初始化父类的对象
Soldier s1;
Person p1=s1; //反过来不可以
这时p1和s1共有的成员(数据成员和成员函数)就会赋值给p1.而s1特有的就不会
2: 函数的覆盖和隐藏
隐藏:比如父类A和子类B,B继承了A,A中有ABC函数,同时B中也定义了同名到ABC函数,这时实例化一个B对 象,调用ABC函数时,调用的是子类的ABC函数,而不是父类的,这时父类的函数ABC就被隐藏(如果 数据成员同名也一样,也是隐藏)了,不过这种情况比较少见,因为父子成员同名没有什么意义
调用时:
B b;
b.ABC(); //调用的是B中的ABC
b.A::ABC(); //这样就是调用A中的ABC,只能这样做
虚函数表指针:当我们实例化一个A的对象的时候(比如A a),在这个a中,除了有数据成员,还有一个虚
数表指针(指向虚函数表),虚函数表中有指向虚函数入口地址的指针,这样就可以找到定义 的虚函数和入口地址
当实例化B的时候(比如B b),如果B类没有定义和父类同名的虚函数,但是B可以继承A的虚函 数,b的虚函数表指针指向b的虚函数表,b的虚函数表里面也有一个指针,这个指针指向A的函 数入口地址,但是当B类也定义了和A同名的虚函数,实例化A的对象和上面一样,而b的虚函数 表和之前的是一样的,不过虚函数表里面指向同名函数的指针指向的是自己定义的虚函数的地 址,不是指向A的虚函数的地址,这就是函数的覆盖
可以通过计算对象的大小来证明虚函数表的存在:
对象的的大小:类的数据成员的大小,不包括函数(如果类没有数据成员,大小为1而不是0,这样是为了标 识其存在)
虚析构函数:值得注意的是,当从堆中用子类实例化父类,销毁时执行的是父类的析构函数,而不会执行子 类的,这样就有可能造成内存泄漏,为解决这个问题,需要在父类的析构函数前加上virtual关
键字,即成了虚析构函数,这样子类也会继承这个虚析构函数,所以子类可以不用加virtual,但
建议还是写上
virtual不能修饰普通函数(比如全局函数),静态成员函数,内联函数(如果修饰内联函数,计算机会
忽略掉inline关键字,),构造函数
3: 多继承和多重继承
多重继承:比如士兵类继承人类,步兵(infantry)类继承士兵类,这就是多重继承
class Person{};
class Soldier:public Person
{};
class Infantry:public Soldier
{};
当实例化一个子类时,会依次执行父类的构造函数,比如:
Soldier s;//会依次执行Person,Soldier,Infantry的构造函数
多继承:比如有个一工人类,一个农民类,农民工类继承工人类和农民类,那么农民工类就是多继承
class Worker{};
class Farmer{};
class MigrantWorker:public Worker,public Farmer
{};
如果实例化一个子类,会先执行哪个父类的构造函数呢?其实是按照子类继承的初始化列表顺序来的,由
于MrgrantWorker是先继承Worker,再继承Farmer,所以先执行Worker的构造函数,再执行Farmer的
4: 虚继承:有些比较复杂的继承既有多继承,又有多重继承,比如菱形继承,这里 人
工人类和农民类分别继承了人类,而农民工又继承了工人类和农民类, 工人 农民 那么问题来了,如果实例化一个农民工,岂不是继承了两次人类,这样肯 农民工
定不是我们想要的,虽然不会报错,这就要用到虚继承了,这里工人和农民称
为虚基类,分别加关键字virtual
c++远征之多态篇
1 多态(包括静态多态和动态多态):是指相同对象受到不同消息或不同对象收到相同消息时产生不同的动作
静态多态(早绑定):比如定义了互为重载的几个函数,那么在编译的时候根据传入的参数就知道要用
哪个函数,这就是静态多态
动态多态(晚绑定):比如定义一个Shap类,然后定义Rect(矩形)类和Circle(圆)类分别共有继承Shap
类,三个类中分都定义了各自的计算面积函数Calcarer(),然后分别用子类实例化
父类Shape *p1=new Circle;Shape *p2=new Rect;那么执行p1,p2的计算面积时,
调用的其实都是父类的函数,为此,需在父类的函数前加virtual修饰,即虚函,
数,建议子类中函数也加上virtual(虽然不加的话编译器会自动加上),这样执行
p1,p2的函数就分别是Circle和Rect的函数了,这样在运行阶段才知道执行哪种
函数就是晚绑定,即动态多态,这里注意要在Shaped的析构函数加上virtual,不 然只会执行父类都析构函数,建议都加上virtual
2:纯虚函数:比如类Shape中
class Shape
{
public:
virtual double caca(); //虚函数
{ //虚函数表中的函数指针,如果函数是虚函数的话,相应指针的 //的值就是相应函数的地址,而虚函数表中对应纯虚函数的函数指针的值为0
抽象类:
含有纯虚函数的类叫做抽象类,抽象类不允许实例化对象
return 0;
}
virtual double calcPerimeter()=0; //纯虚函数
};
仅含纯虚函数的抽象类就是接口类(没有数据成员,仅有成员函数,而且成员函数都是纯虚函数,连构造, 析构函数都没),接口类更多到表达一种能了或协议
4:RTTI:运行时类型识别(Run Time Type Identification)
关键字:typeid,dynamic_cast;
举个例子:
class Flyable{
public:
virtual void takeoff()=0;
virtual void land()=0;
};
class Bird:public Flyable
{
public:
void foraging(){...} //forage 觅食
virtual void takeoff(){...}
virtual void land(){....}
private:
...
};
class Plan:public Flyable
{
public:
void carry(){...}
virtual void takeoff(){...}
virtual void land(){....}
private:
...
};
假如有一个函数void DoSomething(Flyable *p)
{
p->takeoff();
如果是Bird1,则觅食,
如果是Plane,则运输
p->land();
}
这就要用到RTTI了
void DoSomething(Flyable *p)
{
p->takeoff();
cout<
{
Bird *bird=dynamic_cast
bird->foraging();
} //是Plane到代码这里就不写了
p->land();
}
dynamic_cast注意事项:
只能用于指针和引用的转换
要转换的类型2中必须包含虚函数
转换成功,返回子类到地址,失败则返回NULL
typeid注意事项:
type_id返回一个type_info对象的引用
如果想通过基类的指针获取派生类的数据类型,基类必须带有虚函数
只能获取对象的实际类型
想要知道一个数据时什么类型,比如type i;
可以用typeid(i).name();这样就会打印出该变量的类型名
5:异常处理:对有可能发生异常的地方做出预见性的安排
关键字:try...catch...
throw
大致的写法:
void fun1(){
.....
throw 1; //假设遇到某种异常,抛出一个整形数字11
}
int main()
{
try
{
fun1();
}
catch(int){
... //对异常做相应处理
}
return 0;
}
另外,try可以一对多
try()
{
fun1();
}
catch(int)
{ ... }
catch(double)
{ ... }
catch(...) //如果前面的都不能处理跑出到异常,就会交给这里来处理,这里是非常野蛮的,实在处 // 理不了才交给这里,算是最后的挣扎了
{ .....}
//这里都是抛出一个数据值,捕获一个类型,也可以抛出值,捕获值,例如:
char GetChar(const string &aStr,const int alndex)
{
if(alndex>=aStr.size())
{
throw string("ivalid index!");
}
return aStr[alndex];
}
string str("hello world");
char ch;
try{
ch=GetChar(str,100); //抛出了异常就不会执行下面的语句了
cout<
catch(string &aval)
{
cout<
常见的异常:
数据下标越界;
除数为0;
内存不足(情况较少);
异常与多态:
可以定义一个异常的接口类:Exception
然后定义多个细分的子类(HardwareErr,SizeErr,MemoryErr,NetworkErr等)继承Exception,那么当抛出 异常,就都可以用Exception来捕获了
c++远征之模版篇:
1:友元函数和友元类(关键字friend)
1): 友元函数
|
______|______
| |
全局函数 成员函数
a:友元全局函数
class Coordinate{
friend void printXY(Coordinate &c){ //传入的是引用,比指针传入效率跟高,速度更快, 提倡用引用
cout<
public:
Coordinate(int x,int y){
m_iX=x;
m_iY=y;
}
private:
int m_iX;
int m_iY;
};
int main(){
Coordinate coor(1,2);
printXY(coor);
return 0;
}
b:友元成员函数
# include
using namespace std;
class Time; //一定要注意顺序
class Match{
public:
void printTime(Time &t); //这里要用到Time,所以声明
//这里不能函数体,要在后面定义,不然报错
};
class Time{
friend void Match::printTime(Time &t);
public:
Time(int x, int y, int z){
m_iHour = x;
m_iMinute = y;
m_iSecond = z;
}
private:
int m_iHour;
int m_iMinute;
int m_iSecond;
};
int main(){
Time t(19,45,5);
Match m;
m.printTime(t);
system("pause");
return 0;
}
void Match::printTime(Time &t){
cout << t.m_iHour << ":" << t.m_iMinute << ":" << t.m_iSecond << ":" << endl;
}
并不推荐使用友元,因为虽然对于数据的直接访问虽然方便遍了,但在不小心改变数值后不易 察觉,风险与方便并存
2) 友元类
class Printinfo;
class Coordinate{
friend Printinfo;
public:
Coordinate(int x, int y){
m_iX = x;
m_iY = y;
}
private:
int m_iX;
int m_iY;
};
class Printinfo{
public:
Printinfo(int x,int y):coor(x,y){ //这里必须初始化列表
cout << "Printinfo" << endl;
}
void printxy(){
cout << coor.m_iX << " " << coor.m_iY << endl;
}
private:
Coordinate coor;
};
Printinfo已经声明为Coordinate的友元类,就可以任意访问Coordinate的数据成员和成员函数了
注意事项:
友元关系不可传递;
友元关系的单向性;
友元声明的形式及数量不受限制
(友元只是封装的补充,不得已而为之)
2:静态数据成员不依赖对象而依赖于类存在,比如Tank tank;sizeof(tank)中不包括静态数据成员的大小 而且静态数据成员必须单独初始化,例如:
class Tank
{
public:
Tank(string code){
m_strCode=code;
s_iCount++;
}
~Tank(){
s.iCount--;
}
static int GetCount(){
return s_iCount;
}
static int s_iCount;
private:
string m_strCode; //坦克编号
};
int Tank::iCount=0;
可以通过类Tank::s_iCount直接访问s_iCount的值,也可以通过对象访问:
cout<
Tank t2("2");
cout<
void fire(){
cout<
[
m_strCode="01"; //这是不可以的,静态成员函数不会传入this指针,所以无法识别
m_strCode,易知也不能给静态成员函数后面加const
return s_iCount;
}
3:运算符重载(本质:函数重载,关键字:operator)
1)一元运算符重载(以++为例):
a:友元函数重载(全局)
class Coordinate{
friend Coordinate &opetator-(coordinate &coor);//需要传入参数(前置)
friend Coordinate operator++(Coordinate& c,int);//后置的话operator前不加引用,int是表 //示后置的标识
public:
Coordinate(int x,int y)
m_iX=x;
m_iY=y;
}
private:
int m_iX;
int m_iY;
};
friend Coordinate &opetator-(coordinate &coor){
coor.m_iX=-coor.m_iX;
coor.m_iY=-coot.m_iY;
return *this;
}
friend Coordinate operator++(Coordinate& c,int){
Coordinate c1(c);
c.m_iX++;
c.m_iY++;
return c1;
}
使用时
int main(){
Coordinate coor(3,5);
-coor; //相当于operator-(coor);
coor++;//operator++(coor,0); 默认是0,没意义,但能够表达出是调用后置++
return 0;
}
b:成员函数重载:
class Coordinate{
public:
Coordinate(int x,int y)
m_iX=x;
m_iY=y;
}
Coordinate &opetator++(){ //因为是一元运算符,所以不用传入参数
注意前置++和后置++的区别
++m_iX;
++m_iY;
return *this;
}
Coordinate operator++(int){ //这里int只是作为一个标识,表明是后置,而且operator前不 //加引用,不然cot<<(coor++).get(x)的结果是4
Coordinate old(*this);
m_iX++;
m_iY++;
return old;
}
private:
int m_iX;
int m_iY;
};
使用时
int main(){
Coordinate coor(3,5);
++coor; //前置
//相当于coor.operator++();即调用了该函数
coor++;// 后置 coor.operator++(0);//这里系统给定的0没有意义,只是为了表示后置
return 0;
}
2)二元运算符重载
a:友元函数重载
(-)减号:
friend Coordinate operator-(const Coordinate &c1,const Coordinate &c2){
Coordinate temp(0, 0);
temp.m_iX = c1.m_iX -c2.m_iX;
temp.m_iY = c1.m_iY - c2.m_iY;
return temp;
}
!!!注意,这里operator后面不是引用,以-为例,因为-运算符只是为了将两个变量相减之后的值赋给另一个
变量,所以应该是值传递
(<<)输出运算符:
friend ostream& operator<<(ostream &c, const Coordinate &coor){//输出运算符应为一个 ostreaam的对象,故不能用成员函数
c << coor.m_iX << "," << coor.m_iY << endl;
return c;
}
b:成员函数重载
(+号:Coordinate operator+(const Coordinate &c){ //在加的过程当中是不希望相加的数变化 的,加const是一种设计上的规范
Coordinate temp(0, 0);
temp.m_iX = c.m_iX + m_iX;
temp.m_iY = c.m_iY + m_iY;
return temp;
}
([])索引运算符(不能用友元函数重载):
int operatot[](int index){
if(index==0)
return m_iX;
if(index==1)
return m_iY;
}
实现时:
int main(){
Coordinate c1(1, 2);
Coordinate c2(12,4);
Coordinate c(0, 0);
c= c1 + c2;
cout << c.getX() << "," << c.getY() << endl;
c2 = c - c1;
cout << c2.getX() << "," << c2.getY() << endl;
cout << (c1+c2);
cout << c1[0] << endl;
system("pause");
return 0;
}
4:模版函数与模版类
1)函数模版
比如3个函数
int max(int a,int b){return a>b?a:b;}
char max(char a,char b){return a>b?a:b;}
float max(float a,float b){return a>b?a:b;}
如果是比较大的函数,这样(只是函数类型不同的函数)写起来很麻烦,这时就可以用到函数模版 了,当我们传入一个函数类型,系统根据传入的数据类型,返回相应的返回值,通过函数模版生产 的函数就是模版函数
关键字:template(模版) typname class(这里的class不是表示类,而是表示数据类型)
使用方法:
a: 传入的是数据类型
template
T max(T a,T b)
{ return a>b?a:b;
} // 函数模版
int main(){
int ival=max(100,99);//这里根据传入的类型自动实例化一个int类型的模版函数
char cval=max
return 0;
}
b:传入的是变量
template
void display(){
cout<
使用时:
diaplay<10>();
比较简单,就不再说了
c:多个参数的话,用逗号隔开
template
void display(T a,T b)
{
cout< }
2) 类模版:在很多场合下,一个类被用到很多次,一般重次的地方只有数据类型不同,这个时候就要 用到类模版
template
class Myarray{
public:
void display(){ //类内定义和普通类一样
....
}
private:
T *m_pArr;
};
template
void MyArray
...
}
//实例化时
MyAray
特别提醒:模版代码不能分离编译器不能写在多个.h,.cpp文件
5:STL(Standard Template Library)标准模版库(因为非常多,只列举一部分,其他的随用随学)
1) voctor(向量)(可以把它看作一个数组,只不过相对于传统的数组,功能要强大锝多,可 以根据存储的元素的个数自动地变长或者缩短)
a:本质:对数组的封装
b:特点:读取能在常时间内完(无论是存10个数据还是10000个数据,都能很快找出我们想要的数据
c:初始化vector对象的方式
vector
vector
vector
vector
d:vector常用函数
empty() 判断向量是否为空,bool类型
begin() 返回向量迭代器首元素
end() 返回向量迭代器末元素的下一个元素
chear() 清空向量
front() 第一个数据
back() 最后一个数据
size() 获得向量数据大小
push_back(enem) 将数据插入向量尾
pop_back() 删除向量尾部数据
e:实现(记得加# include
int main(){
vector
ivec.push_back(10);
cout<
cout<
return 0;
}
e:遍历(随你使用哪种方法,不过有些情况只能使用迭代器)
(常用遍历方法)
for(int k=0;k
(用迭代器遍历)iterator(迭代器,迭代程序) 相当于一个指针
int main(){
vector
for (; citer != svec.end(); citer++)//end()是最后一个元素的下一个元素,所以这样 //写是没问题的
cout << *citer << endl;
system("pause");
}
/***************************************************************************************/
ps:vector的功能比数组强大,但付出的代价是效率低,如果需要的是长度固定的数组,使用数组是更佳的, 选择但代价是不那么方便。为此,c++11新增了模版类array,也位于std中,与数组一样array对象的长度也 是固定的,也使用栈(静态内存分配),而不是自由存储区,因此其效率与数组相同,但更方便,更安全,
要创建array数组,需包含array头文件,
/***************************************************************************************/
array
2)链表(插入速度快)(头文件#include)
list
list1.push_back(1);
list1.push_back(2);
list1.push_back(3);
list1.push_back(4);
/*for (int j = 0; j< list1.size(); j++) 这样是不行的,必须使用迭代器
cout << list1.[j] << endl;*/
list
for (; ltor != list1.end(); ltor++)
cout << *ltor << endl;
cout << endl;
3)mapping(映射)(头文件# include