(未整理的草稿纸)
class A
{
public:
A(int i) {};
~A() {}
};
int main()
{
A a(); //声明了一个返回A类型的函数a
A b = new A;
A c = new A(); //两者语义一致
}
A a(0);
int main()
{
A b(1);
A c = 2; //初始化阶段,等号和括号的意义相同
}
a构造->b构造->c构造->c析构->b析构->a析构
void f(int i)
{
if (i < 10)
goto jump1;
X x1;
jump1:
…
}
编译出错!跳过了x1的初始化
void f(int i)
{
switch(i)
{
case 1:
A a; //int a
case 2:
A b; //int b ————-This would be OK !
…….
}
}
switch语句中同样不允许定义类,因为可能跳过初始化但下一个case由于没break依然能用
Aggregate initialization:
int a[5] = { 1,2,3,4,5 };
int b[6] = { 5 };
int c[] = { 1,2,3,4 }; //获取数组深度:sizeof c / sizeof c;
struct X { int i; float f; char c };
void f()
{
X x[3] = { {1, 1.2, ‘a’}, {2, 2.1, ‘b’} }; //第三个X全0初始化,没有构造函数才能这么写!
X y[3]; //都是随机数!
}
*
class Display
{
Display(int limit) { … };
};
class Clock
{
Display hour(24);
Display minute(60); //编译出错!只能在类的定义中调用其它类的初始构造
}
class Clock
{
Clock() : hour(24), minute(60) {};
//Clock : minute(60), hour(24) {}; //仍然是hour先初始化,取决于成员变量顺序
Display hour;
Display minute;
}
Student::Student(string s) :name(s) //调用拷贝构造函数创建string
Student::Student(string s) { name = s; } //调用默认构造函数创建string后再进行赋值,慢!
符号表
extern “C”
#ifdef __cplusplus //被自动定义在C++文件中
class A
{
void f() const { cout << “a”; } //const关键词作用于this指针,可用于构造重载
void f() { cout << “b”; }
private:
int I;
}
A g;
A& k()
{
return g; //导致输出b
}
const A& k()
{
return g; //导致输出a
}
int main()
{
k().f();
}
文件作用域下的const变量默认为static,除非定义为extern const int a = 123;
inline函数一定要写在头文件中么?放在cpp中编译也能过但只有当前cpp能用,不推荐!
函数返回引用?
static成员变量实际上是全局变量
static成员函数在定义时不能再加static关键词,只能在类中的声明加static
static成员函数可以通过A::f()调用也可以通过A a; a.f();调用
//head.h:
class A
{
private:
const static int limit;
public:
….
}
//test.cpp
const int A::limit = 100; //必须在文件中定义静态成员变量,若为const必须初始化
void main()
{
using namespace std;
using namespace MyLib;
foo(); //先寻找当前块,再寻找std空间,最后寻找MyLib空间
Cat c;
c.Meow();
cout << “Hello” << endl;
}
head1.h:
namespace A
{
void f();
}
head2.h:
namespace A
{
void g();
}
#include “head1.h”
#include “head2.h”
using namespace A;
f();
g(); //均可用
在namespace中定义变量并在其它cpp中使用:
head.h:
namespace n1
{
extern int a;
}
namespace n2
{
extern int a;
}
vars.cpp:
int n1::a;
int n2::a;
main.cpp:
using namespace n1;
a = 1;
class A
{
int I;
void ptr() { cout << “A” };
A(const int n) : i(n){};
}
class B : public A
{
int k;
void ptr() { cout << “B” };
B() : A(47), k(31){}; //通过初始化列表给父类的初始化传递参数
}
B b;
b.ptr() //输出B;
B::f()可以通过A::ptr()调用父类A的ptr函数,B类的外部只能调用B的ptr函数
class A
{
public:
int I;
}
class B : public A
{
int k;
B() : k(31), i(47); //Error!任何情况下父类的成员必须由父类的构造函数初始化
}
父类的构造函数先于子类的构造函数执行,析构函数相反
class A
{
public:
void prt(){ cout << “A1” }
void prt(const int n){ cout << “A2” }
void prt(const double d){ cout << “A3” }
}
class B : public A
{ … }
B b;
b.prt() //输出A1
b.prt(123) //输出A2
b.prt(1.23) //输出A3
改变B类的定义为
class B : public A
{
prt(){ cout << “B” }
}
b.prt() //输出B
b.prt(123) //Error!需要调用b.A::prt(123)
b.prt(1.23) //Error!需要调用b.A::prt(1.23)
class B{ using A::prt; … } //可以直接调用所有名为prt的父类函数
把子类的对象当做父类的对象看待:up-casting
class A
{
virtual void prt(){ cout << “A” }
};
class B
{
void prt(){ cout << “B” }
};
A a;
B b;
Ap = &b; //up-casting
a.prt(); //输出A
b.prt(); //输出B
p->prt(); //输出B!根据指向变量的实际类型调用prt,动态类型!
如果class B没有prt函数,则调用父类A的prt函数输出A
class A class B : public A
{ {
virtual void f(); virtual void f();
int I; }
}
32位下A类占用8个字节,前4字节是Virtual-Pointer,指向Virtual-Table中的项,Virtual-Table中存放虚函数指针。每个对象拥有一个Virtual-Pointer,每个类拥有一个Virtual-Table。
Virtual-Pointer在构造函数中确定,对象之间互相赋值不会赋值Virtual-Pointer,对象创建以后没有任何方式改变其Virtual-Pointer,Virtual-Pointer代表了对象的动态类型
B b;
A a = b;
a->f() //调用A类的f函数
B b = new B;
A a = b;
a->f() //调用B类的f函数
当类可能作为基类被over-write时,析构函数应该为virtual。否则派生类析构时只会调用基类的析构函数。A a = new B; delete a; 仅仅调用了A类的析构函数
友元可以提高程序的运行效率但是会破坏封装性。
友元关系不可以被继承。
int main()
{
int a[5][5];
int(p)[4]; //数组指针,指针数组是int p[4]
p = (int()[4])a;
printf(“%d\n”, &p[4][2] - &a[4][2]);
}
输出结果为-4,p为数组指针。p是一个指向包含4个int元素的一维数组的指针,p[i]相对于p[i-1]增长了4个元素,因此p[4][2]为第18个元素而a[4][2]是第22个元素。两者的地址虽然相差16,但是计算的时候是两个指针类型的变量在计算,因此输出的结果为-4
空的类占据1个字节的空间,假设class A为空类,为了防止A a和int i的地址一样,强行为A类分配1个字节的空间保证地址的独立
Person::Person(const Person& p)
{
name = p.name; //private的保护针对类而言而非对象,p私有成员可以访问
}
Person baby_a(“Fred”);
Person baby_b = baby_a;
Person baby_c(baby_b); //三者均为初始化而非赋值
const A operator+(const A& b);
A x, y;
y = x + 3 //必须要有需要一个int类型参数的构造函数,隐式构造A的对象
x+3相当于调用x.operator+(A(3));
3+x要么将重载函数写为全局函数,传递两个参数,将3利用构造函数构造为A(3);要么编写operator int强制类型转换重载将x转换为int。
运算符的重载可以写在类中作为类的成员函数:
A operator+(const A&b);
也可以写在全局中,并在类内声明为友元:
Friend A operatr+(const A&a, const A&b); //需要两个参数
=(赋值)、()、[](取下标)、->、->都必须作为成员函数重载
其它二元运算符重载建议作为全局函数编写
+ - / % ^ & | ~
const T operatorX(const T& l, const T& r);
! && || < <= == >= >
bool operator(const T& l, const T& r);
[]
E& operator;
++ — (考虑前缀和后缀)
const A& operator++()
{
i++; return this;
}
const A operator++(int)
{
A a(this); i++; return a
}
T& operator=(const T&rhs)
{
delete m;
m = new E[100];
for (…)
m[i] = rhs.m[i];
return this;
}
注意c=c这样的赋值!delete m后就无法拷贝,需要在一开始判断赋值端是否是本身。
不带有指针的类可以用默认的赋值,带有指针的类的赋值就可能涉及内存delete
class A
{
operator B(); //可以编写基本类型强制类型转换,但不建议编写operator T()
}
class B
{
B(A){};
}
A a;
B b = a; //二义性!
模板函数实际上时声明而不是定义,编译器在编译时会自动生成相应的代码。
template
swap(T&a, T&b)
{
T temp = a;
a = b;
b = temp;
}
string s1, s2;
//swap(string&, string&) ; 编译器会自动插入此函数声明。
swap(s1,s2);
倘若我们同时有swap(T&, T&)与swap(string&, string&),swap(s1,s2)会调用用户自定义的函数
类模板的所有成员函数都是函数模板
template
class Vector
{
…
}
template
Vector
{
m_element = new T[size];
}
Template
Vector
Vector.cpp
template
Vector
…
…
Main.cpp
Vector
编译出错!Vector.cpp中虽然写出了所有成员函数,但都只是声明,找不到定义。必须把所有的东西都放到头文件!与inline函数类似!
模板类的静态成员变量,找不到合适的地方存放,因此模板类不能使用静态成员变量。
template
Int Vector
除非只用在单个CPP中,所有都定义在CPP,template
Main.cpp
void f(Vector
int main()
{
Vector
f(v1);
}
a.cpp
void f(Vector
{
…
}
Main.cpp与a.cpp两个cpp是分开编译的,编译的时候编译器分别为它们生成了一份Vector
catch(…) //能够抓住任何类型的异常,但是无法获得异常的对象
catch的三种情况:
1. throw在try内,且有适当的catch匹配,则直接执行并往下走。
2. throw在try内,且没有适当的catch匹配,则一路退出直到找到适当的catch。
3. throw不在try内,则一路退出直到找到适当的catch。
class A;
class B : public A;
….
catch(A){…}
catch(B){…} //永远不会被执行
建议的使用方法:不要抛出new的对象而抛出本地变量,接收变量的引用。
void f() throw(int, double) //只能抛出int和double类型否则出错
void f() throw() //不能抛出任何异常
void f() //可以抛出任何异常
构造函数和析构函数中不要抛出异常,也不要申请资源(例如new,fopen),否则A *p = new A(); 中抛出异常时,new出来的空间会成为野内存空间,p还没有被赋值;delete时调用析构函数但是析构函数抛出异常跳过了delete的内存回收。
区别以下两个区别,如果ABCD四个操作时一个连贯的操作,则应该将他们写在同一个try语句中,保证任何错误都会中止接下来的操作。右边的逻辑是不对的!
try try{A}
{ catch(A){…}
A; B; C; D; try{B}
} catch(B){…}
catch(A){…} try{C}
catch(B){…} catch(C){…}
catch(C){…} try{D}
catch(D){…} catch(D){…}