1、sizeof
C语言关键字,获取数据在内存中所占用的空间,以字节为单位计算。
int, float都是4
double, long long都是8
char是1
short是2
指针,与操作系统位数有关,32位为4,64位为8
类的大小:空类大小为1,空类也要实例化,所谓类的实例化就是在内存中分配一块地址,每个实例在内存中都要有独一无二的地址。空类也会被实例化,编译器会给空类添加一个字节。
类的大小为类的非静态成员数据的类型大小之和
普通成员函数与sizeof无关
虚函数由于要维护在虚函数表,所以占据一个指针大小
第五章 循环和关系表达式
1、
2、类别别名:
(1) #define FLOAT_POINTER float *
FLOAT_POINTER pa, pb; 预处理器置换将该声明转换成 float * pa, pb; // pa 是指针,pb就是一个float
(2) typedef char byte 不会出现上述问题是最佳的选择
3、 cin.get(name, arSize).get() // 输入长度为arSize的name接受换行
// 发送给cin的输入被缓冲。这意味着只有用户按下回车之后,他输入的内容才会发送给程序 所以 # 后面可以跟其他的字符。 // cin 将忽略空格和换行符,统计时候不算空格 char ch; int cnt = 0; cin >> ch; while(ch != '#') { cout << ch; ++cnt; cin >> ch;
// cin.get(ch); 此时就可以读取空格了 } cout << endl << cnt << "characters read ";
4、文件尾条件
检测到EOF后,cin将两位(eofbit 和 failbit ) 都设置为1。可以通过成员函数 eof() 来查看 eofbit 是否被设置;如果检测到 EOF ,则 cin.eof() 将返回bool 值true,同样eofbit 和 failbit 被设置成1,则 fail() 成员函数返回 true
注意,eof() 和 fail() 方法报告最近读取的结果;也就是说,他们在事后报告,而不是预先报告
ctrl + z + 回车 结束
while ( cin.fail() == false ) { }
int ch = cin.get() //返回的是一个整数 EOF时为-1
cin.get(ch) //返回的是 true or false
5、读取数字的循环
int a; while(cin >> a) //当输入整形时正常运行,当输入错误例如输入字符或者OEF都导致cin返回false { cout << a << endl; }
当输入字符出现错误,然后在继续读取
for(int i = 1; i < Max; i++) { cout << "round #" << i + 1 << ": "; while (! (cin >> golf[i] ) ) { cin.clear(); // 重新设置输入状态 while(cin.get() != ' ') // 将读取的错误内容清除掉 continue; cout << "Please enter a number: "; } } 如果输入失败(即!(cin>>golf[i])表达式的值为true),则进入while循环,然后使用clear重置输入,紧接着cin.get()从错误输入的第一个字符开始依次读取错误字符,直到遇到换行符(注意,这里的换行符是前面cin在从键盘读取读取输入时,在行尾自动添加的),特别注意:这里的内层while循环只包含一条语句,即continue语句,如果测试条件为真,则continue使程序返回到while(cin.get() != 'n')语句,从而达到依次读取字符的目的。当cin.get()读取到换行符时,测试条件为false,内层while不在执行循环体,至此程序使用cin.get()达到了删除整行的作用,此时程序执行下一条语句:cout<<" Please enter a number: ";提示用户重新输入一个数字!
6、文件的输入输出
写到文本文件当中 头文件fstream
char automobile[50]; int year; double a_price; double b_price; ofstream outFile; outFile.open("carinfo.txt"); cout << "Enter the make and model of automible: "; cin.getline(automobile, 50); cout << "Enter the model year: "; cin >> year; cout << "Enter the original asking price: "; cin >> a_price; b_price = a_price * 0.913; outFile << fixed; outFile.precision(2); outFile.setf(ios_base::showpoint); outFile << "Make and model: " << automobile << endl; outFile << "Year: " << year << endl; outFile << "was asking $: " << a_price << endl; outFile << "Now asking $: " << b_price << endl; outFile.close();
从文件读取:
char filename[SIZE]; ifstream inFile; // 声明ifstream变量 cout << "Enter name of data file: "; cin.getline(filename, SIZE); inFile.open(filename); // 调用open() 打开文件 if(inFile.is_open() == 0) // 判断是否被成功打开 { cout << "Could not open the file " << filename << endl; cout << "Program terminating. "; exit(EXIT_FAILURE); // cstdlib库函数中定义了一个同操作系统通信的参数值EXIT_FAILURE。函数exit()终止程序 } double value; double sum = 0.0; int cnt = 0; inFile >> value; while(inFile.good()) // 判断读取是否正确,EOF 或者格式是否匹配 { ++cnt; sum += value; inFile >> value; } //如果结束就分情况判断是哪种情况引起的结束 if(inFile.eof()) // 遇见EOF cout << "End of file reached." << endl; else if(inFile.fail()) //类型不匹配 cout << "Input terminated by data mismatch." << endl; else cout << "Input terminated for unknown reason." << endl; if(cnt == 0) cout << "No data processed." << endl; else { cout << "Items read: " << cnt << endl; cout << "Sum: " << sum << endl; cout << "Average: " << sum / cnt << endl; } inFile.close();
7、动态分配 结构体数组
struct node { string name; double money; int flag; }; int main() { int num; cin >> num; cin.get(); //接受回车 node *p = new node[num]; // node类型的指针p for(int i = 0; i < num; i++) { getline(cin, (p + i) -> name); // 通过指针 + 偏移 cin >> (p + i) -> money; cin.get(); }
第七章 函数 -- c++的编程模块
1、
1 int sum_arr(const int * Begin, const int * End) // 求一个数组任意区间和, const 表示不可更改 2 { 3 int * pt,sum = 0; 4 for (pt = Begin; pt != End; pt++) 5 sum += *pt; 6 return sum; 7 }
2、指针与 const
const int a = 10; //a是一个常量,不允许修改,但是可以通过一级指针进行修改
int *pt = &a;
*pt = 100; //此时便可以修改const
int age = 39; const int * pt = &age; // pt 指向一个const int 因此不能用pt来修改这个值,换句话来说,*pt的值为const 不能通过 *pt = ?来修改 // 但可以修改 pt 值,即将一个新地址赋给pt int sage = 80; pt = &sage; 此时 *pt就是80了
int * const pt = &age; //表示不能修改指针的位置,但是可以修改值,也就是说 pt = &sage,是不行的,但是可以*pt = ?来修改值
const int* const pt = &age //表示此时pt只能指向age的位置,且不能修改age处的值 --------------------------------------- int sloth = 3; const int * ps = &solth; int * const finger = &solth; // 这种声明使得finger只能指向sloth,但允许用finger 来修改 solth 值 总结: finger 和 *ps 是 const,而*finger 和 ps 不是const
指针存放的是地址,在32位的系统中 任何类型(int*,char *, float等等)的指针都是 4 字节
空指针:NULL
野指针:指向未知位置的指针
如果数据类型本身不是指针,则可以将const数据或非const数据的地址赋给指向const的指针,而只能将非const数据的地址赋给非const指针
const float g_earth = 9.80; const float * pe = &g_earth //valid const float g_moon = 32.0; float * pm = &g_moon; // invaild 只能将非const数据类型的地址赋给非const const int month[12] = { }; int sum(int arr[], int n) int j = sum(month, 12); // invaild、
const double * const stick = & trouble // 此时的stick只能指向trouble,而stick不能用来修改trouble的值,简而言之 stick 和 *stick 都是const ------------------------------ void show_array(const double arr[], int n) //第一个参数其实也可以写成 const double * arr,也就是说不能修改arr指向的值,即不能修改数组的值
2、函数和二维数组
int sum ( int (*arr) [4], int size) // 四个指向int的指针组成的数组 int sum (int arr[][4], int size) // 含义同上 int *arr2[4] //一个指向由4个 int组成的数组指针 ar2[r][c] = *(*(ar2 + r ) + c) // same thing ar2 // 指向第一行的的(四个指针数组) ar2 + r // 指向第 r 行的(四个指针数组) *(ar2 + r) //指向这一行第一个数的指针,类似 ar[r] *(ar2 + r) + c // 指向 这一行这一列的指针 * (*(ar2 + r) + c) // so...
3、
while(*str) { // 遍历一个字符串,当结束' ‘(空字符)出现时结束, str++; }
4、c++11提供array
#include <array> array<double, 5> expenses; // 定义了一个数组,5个double类型 void fill( array<double, 5> * pa) // 指针传递array数组 { for( int i = 0; i < 5; i++) cint >> (*pa)[i]; //pa是指向array对象的指针,因此*pt是为这种对象,而(*pt)[i] 是该对象的一个元素 }
5、函数指针
int think(); process(think); // 参数是函数地址,直接传递函数名 thought(think()); //参数是函数的返回值 2.声明函数指针: double pam(int); double (*pf) (int); //与pam()声明类似,这是将pam替换成了(*pf)。pam是函数,因此(*pf)也是函数,如果 pf 就是函数指针 double *pf (int)意味着 pf() 是一个返回指针的函数 double (*pf)(int)意味着pf是一个指向函数的指针 ------------------------------------------------------------------------------------ 将函数的地址付给它 double pam(int); double (*pf) (int); pf = pam; // pf指向pam函数 ------------------------------------------------------------------------------------ 使用指针来调用函数 double betsy(int lns) { return 0.5 * lns; } void estimate(int lines, double (*pf) (int) ) { cout << lines << " lines will take "; cout << (*pf) (lines) << " hour(s)" << endl; // 直接使用(*pf)(参数)来调用函数,c++也允许 pf(lines)这样来调用,两种调用都可以 } int main() { cin >> code; estimate(code, besty); }
第八章 函数探幽
1、
内联函数主要是用来代替宏的,因为宏有缺陷,主要就是宏展开后是一个文本,尤其是在四则运算过程中会丢掉想要表达的一下
inline double squrae(double x) {return x * x;} //内联函数,编译器使用相应的函数代码替换函数调用。程序无需调到另一个位置处执行代码在跳回来。快但占用内存,空间换时间思想
------------------------------------------------ #define SQUARE(X) X*X //而是通过文本实现 SQUARE(3.5 + 5.4) 展开后为 3.5 + 5.4 * 3.5 + 5.4
int rats = 101; int &rodents = rats; // rodents是rats的引用,他们的值和地址都是相同的,并且一个变化另一个也变化 --------------------------------------------------------------------------- 临时变量 和 const 如果引用参数是const,编译器在下面两种情况下会生成临时变量 1. 实参类型正确,但不是左值(左值是指变量,数组等), 不是左值形如 x + 1; 2.实参类型不正确,但可以转换成正确的类型 // const状态就不可以 double recube (const double & ra) { return ra * ra * ra; } int main() { double rd = 3.2; double c1 = recube(rd + 2); // 可以的,这就 不是左值的情况 int rrd = 4; double c2 = recube(rrd) //也是可行的 } // 在上面情况编译器都将生成一个临时匿名变量,并将ra指向它。临时变量只在函数调用期间存在,此后编译器可以随时删除。
2、将引用用于结构体
1 void display(const free_throws & ft); //不希望函数修改传入的结构,就这个函数而言也可以按值传递结构,但与复制原始结构的拷贝相比,使用引用可节省时间和内存 2 free_throws & accumulat(free_throws & target, const free_throws & source) { 3 target.attempts += source.attempts; 4 target.made += source.made; 5 return target; 6 } // 返回是一个引用 7 dup = accumulat(team, five) // 将team的值赋给dup 8 独特的一个用法: 9 accumulat(dup, five) = four // 由于函数返回的是指向dup的引用,所以上述代码与下面的代码等效 10 accumulat(dup, five); 11 dup = four; 12 2.为何要返回引用 13 //传值是将这个值复制到一个临时位置,在由调用程序使用 14 //返回引用:就是直接复制,不用临时变量,效率高 15 3.返回引用注意的问题 16 1.避免返回的是一个指向临时变量的引用,因为调用完函数后他就不存在了 17 2.一般简单方法返回一个作为参数传递给函数的引用 18 3. const free_throws & clone (free_throws & ft) { 19 free_throws * pt; 20 *pt = ft; 21 return *pt 22 } //第一条语句创建一个无名的free_throws结构,并让指针pt指向该结构,因此*pt就是该结构。函数声明表明,该函数将返回这个结构的引用。 23 就可以这样使用该函数 :free_throws & jolly = clone(there) 24 这使得jolly成为新结构的引用。这个方法存在一个问题:在不需要new分配的内存是,应使用delete来释放他们。调用clone()隐藏了对new的调用,这使得以后很容易忘记delete来释放内存 25 4、为何将const用于引用返回类型 26 accumulate(dup, five) = four; 27 在赋值语句中,左边必须是可修改的左值。左边的字表达式必须标识一个可修改的内存块。在这里,返回指向dup的引用,确实标识是一个这样的内存块。 28 常规(非引用)返回类型是右值--不能通过地址访问的值
3、函数重载
函数重载原理:可能是编译器将重载函数自己定义另外一个名字,
如果没有匹配原型并不会自动停止使用其中的某个函数,因为C++将尝试使用标准类型转换强制进行匹配。如果多个函数原型都可转换,将拒绝这种函数调用
double cube (double x); double cube(double & x); //类型引用和类型本身视为同一个特征标 //匹配函数时,并不区分const 和 非const变量 //返回类型可以不同,但特征标也必须不同 long gronk(int n, float m); double gronk (float n, float m);
const与重载:
int M(int a);
int M(const int a);
从调用者的角度看,所有的形参都是无法修改实参值的,语义相同,这样构不成重载。
int M(int &a);
int M(const int &a);
const引用无法修改引用对象值,但是普通引用却可以,语义不同,可以构成重载
4、函数模板
template <typename T> // typename可以用class替代 void Swap(T &a, T &b); int main() { swap( a, b); 可以定义各种类型的a,b; return 0; } template <typename T> void Swap(T &a, T &b) { T temp; temp = a; a = b; b = temp; } //函数模板不能缩短可执行程序。最终编译的时候,生成各自的版本 //好处就是生成多个函数定义简单、可靠
5、重载的模板
template <typename T> void Swap(T a, T b); template <typaname T> void Swap(T * a, T * b, int n); //重载,参数个数不同 ------------------------------------------------------------------- //实例化和具体化 编译器使用模板为特定类型生成函数定义时,得到的是模板实例。例如,函数调用Swap(i,j)导致编译器生成Swap()的一个实例,该实例使用int类型。模板并非函数定义,但使用int的模板实例是函数定义。这种实例化方式被称为隐式实例化。显式实例化直接名利编译器创建特定的实例,如Swap<int>() 标准格式:template void Swap<int>(int, int); 显式具体化使用下面两个等价的声明之一: template <> void Swap<int> (int &, int &); template <> void Swap(int &, int &); 区别在于,这些声明的意思是"不要使用Swap()模板来生成函数定义,而应使用专门的int类型显式的定义函数定义“。这些原型必须有自己的函数定义。显式具体化声明在关键字template后包含<>,而显式实例化却没有
第九章 内存模型和名称空间
1、单独编译:
2、自动存储持续性:在函数定义中生命的变量(包括函数参数)的存储持续性为自动的。他们在程序开始执行其所属的函数或代码段使被创建,执行完函数或代码块,内存释放
静态存储持续性:在函数定义外的变量和使用的关键字static定义的变量的存储持续性
线程存储持续性(C++ 11)
动态存储持续性:用new运算符分配的内存将一直存在,直到delete运算符将其释放或程序结束为止
3、作用域:描述了名称在文件的多大范围内可见。
链接性:描述了名称如何在不同单元间共享。链接性为外部的名称可在文件间共享,链接性为内部的名称只能由一个文件中的函数共享。自动变量的名称没有链接性,因为他们不能共享。
4、作用域解析符::
// cexternal.cpp double warming = 0.3; int main() { return 0; } // support.cpp extern double warming; // 另一个文件可以直接用上一个文件的变量warming void local() { double warming = 0.8; cout << warming << endl; //显示0.8, cout << :: warming << endl; //显示全局变量0.3 }
5、单定义规则:变量只能又一次定义。
静态变量 static 只能在自己文件中使用,且会覆盖掉同名的其他文件的全局变量
6:存储说明符 (1) auto :在C++11中不再是说明符 (2)register (3)static (4) extern (5) thread_local (C++11 新增的) (6)mutable :
mutable:即使类或者结构体变量为const,其某个成员也可以被修改
struct date { char name[20]; mutable int access; }; const date veep = { "fafaf", 21}; veep.access++ // allowed
7、再谈const
extern const int states = 50;
在C++来看,全局const定义就像使用了static说明符一样,定义常规外部变量时,不必使用extern关键字,但在使用该变量的其他文件中必须使用extern。然而,请记住,鉴于单个const在多个文件之间共享,因此只有一个文件可对其初始化
8、函数和链接性、语言链接性
static int private(double x); .... static int private(double x) { //该函数只能在这个文件中可见,还可意味着可以再其他文件定义同名的函数 ... }
//另一种形式的链接性--语言链接性 C语言中一个名称只对应一个函数,为了满足内部需求,C语言编译器可以将spiff这样的函数名翻译成_spiff。这被称为C语言的链接性。但在C++中,同一个名称可以对已多个函数,必须将这些函数翻译成不同的符号名称。C++编译器执行名称矫正或者名称修饰,为重载函数生成不同的符号名称。spiff(int)转换成_spiff_i等。。 C++程序中使用C库中的预编译的函数,可以用函数原型来指出要使用的约定: extern "C" void spiff(int); //解决c++中调用c语言 extern void spiff(int) // c++ extern "C++" void spiff(int)
9、存储方案和动态分配
//使用new运算符的初始化 int * pi = new int(6); //C++ 11可以初始化常规结构或数组 struct where { double x, double y, double z}; where * one = new where {2.5, 5.8, 7.2}; int * ar = new int [4] {2, 3, 4, 5}; -------------------------------- 定位new运算符:new要在堆里找到一个足够满足要求的内存块,还有一种变体,被称为定位new运算符,他让您能指定要使用的位置 #include <new> // 必须使用new头文件 struct chaff { char dross[20]; int slag; }; int buffer1[50; int buffer2[50]; int main() { charff * p1; char * p2; p1 = new (buffer1) chaff; // 从buffer1中分配空间给结构chaff p2 = new (buffer2) int[20]; // 从Buffer2中分配空间给一个包含20个元素的int数组 } ------------------------------------ 定位new运算符不跟踪哪些内存单元已经被使用,它默认从头开始,如果有就覆盖。除非给了指定偏移 pd2 = new (buffer + N * sizeof(double) ) double [N];
void * 是指空类型的指针 void * a; int * b; float * c; b = c // 错误 a = b //可以,其他所有都能赋值给空类型 b = a // 错误, 空类型赋值给其他需要进行强制类型转换 b = (int* ) a
//void* 需要注意的是,他一旦接受其他类型,就不能再转换成另一个类型
如下
#include <stdio.h>
int main (){
int a= 10;
void *b = &a;
printf("int a = %d
",a);
printf("void (int *)b =%d
",*(int *)b);
printf("void (double *)b =%d
",*(double*)b); //编译器并不会报错但是其结果却有点出人意料
return 0;
}
void有两个用处
1、表示函数返回不需要返回值
2、对函数参数类型限制:表示该函数不接受参数,如 int fun(void)
11、名称空间
namespace Jack { //一个Jack的命名空间 double pail; void fetch(); int pal; struct Well {...}; } //using 声明 和 using 编译指令 //using 声明: 使一个名称可用 using Jack:: pail; //就相当于声明一个变量 double pail; //所以这句是不允许的,已经声明了一个 //using 编译指令:使所有的名称都可用 using namespace Jack; // 然后就可以随便的使用名称空间的变量了 int pail; // 也是允许的只不过把原来的覆盖掉 ------------------------------------------- 同一名称空间的相同名称的变量表示不同的内存单元 jack::pal 和 jill:: pal ------------------------------------------------- using namespace Jill // 只是该名称空间可以在这个文件或者函数内可用 超过了就不能用了。 编译指令 如果有相同的就会发生覆盖,必要时可以用作用于解析运算符进行使用 Jill::fet 表示使用Jill里面的, ::fet表示使用全局的 ------------------------------------------------------- using声明比using编译指令安全
名称空间其他特性: //可以嵌套 namespace elements { namespace fire { int flame; ... } float water; } //这里flame是指 element::fire::flame。同样可以使用下面using编译指令使内部的名称可用using namespace elements::fire; //可以给命名空间创建别名 namespace mvft = myth::elements::fire using mvft::flame 未命名的名称空间 namespace { int ice; int bandycoot; } //未命名的空间跟全局变量相似,由于没有名称,不能显示的使用using编译指令和using声明。不能再未命名空间所属文件之外的其他文件中使用该名称空间的名称。其实就是static的替代品
第十章 对象和类
1、结构默认是public,类默认是private,每个新的对象都有自己的存储空间,用于存储其内部变量和类成员。但同一个类的所有对象共享同一组类方法。构造函数的参数名不能与类成员相同
2、
使用构造函数: Stock food = Stock("World Cabbage", 250, 2.5); //显式的调用 Stock garment("Furry Mason", 50, 2.5); //隐式的调用 还可以用new来分配 Stock * pstock = new Stock("Electroshock Games", 18, 19.0); ----------------------------------- 默认构造函数 Stock fluffy_the_cat; C++自动提供默认构造函数。默认构造函数如下 Stock::Stock() {} //无参数,不含值 如果已经声明了定义了其他带有参数的构造函数就不能使用默认构造函数 定义默认构造函数两种方式: 1、给已有构造函数所有参数提供默认值 Stock(const string & co = "Error", int n = 0, double pr = 0.0); Stock stock; //直接调用 2、Stock () {} Stock stock; //一样调用 或者 Stock stock = Stock(); //只能存在一个默认构造函数
3、析构函数:~Stock// 在类名前面加上~
什么时候调用析构函数由编译器决定,通常不应再代码中显式的调用析构函数。如果创建的是静态存储类对象,其析构函数在程序结束时自动调用,如果创建的是自动存储类对象,其析构函数在程序执行完代码块时自动调用,如果对象时通过new创建的,程序驻留在栈内存,当使用delete来释放内存是,其析构函数将自动被调用。最后程序可以创建临时对象来完成特殊操作,在这种情况下,程序在结束该对象的使用时自动调用其析构函数。
如果没有析构函数,编译器隐式的声明一个默认析构函数。
4、
using std::cout; cout << "Using constructor to create new objects "; Stock stock1("NanoSmart", 12, 20.0); Stock stock2 = Stock("Boffo Objects", 2, 2.9); // 对stock1和stock2初始化 cout << "Assigning stock1 to stock2: "; stock2 = stock1;//可以赋值 cout << "Listing stock1 and stock2: "; stock1.show(); stock2.show(); cout << "Using a constructor to reset and object. "; stock1 = Stock("Nifty Foods", 10, 50); // stock1已存在,这不是初始化,所以这是通过让构造函数创建一个新的、临时的对象,然后将其内容复制给stock1来实现的,随后程序调用析构函数,以删除该临时对象 cout << "Revised stock1: "; stock1.show(); //main函数结束时,其局部变量将消失,由于自动变量是放在栈中,所以后创建的先被删除
5、
const 成员函数: void show() const;保证不改变调用对象的值 函数定义: void Stock :: show() const;
6、this指针
如果方法需要引用整个调用的对象,则可以使用表达式*this,在函数的括号后面使用const限定符将this限定为const,这样将不能使用this来修改对象的值 this是地址
void Stock :: show() cosnt {
}
top.show();
转换为下面这样的C-风格定义
void show(const Stock * this) {
}
show(&top);
7、对象数组
Stock stock[STKS] = { Stock("NanoSmart", 12, 20.0), Stock(), Stock("Monolithic Obellisks", 120, 3.23), Stock("Fleep Enterprise", 60, 3.3), };
8、类作用域
在类中定义的名称的作用域都是整个类;在不同类中使用相同的类成员名而不会发生冲突
声明类只是描述了对象的形式,并没有创建对象,在创建对象之前没有用于存储值得空间
class Bakery {
private:
const int Month = 12;
double costs[Month]; //是不合法的,因为在创建对象之前Month是没有值的
//在类中定义常量的方式--使用关键字 static
class Bakery {
private:
static const int Month = 12;
double cost[Month];
}//创建一个名为Month的常量,该常量与其他静态变量存储在一起,不是存储在对象中,因此只有一个Month常量,被所以Bakery对象共享。
第十一章 使用类
1、运算符重载
//Time Sum(const Time & t) const; Time operator+(const Time & t) const; Time operator-(const Time & t) const; Time operator*(double n) const; Time Time::operator+(const Time & t) const { Time sum; sum.minutes = minutes + t.minutes; sum.hours = hours + t.hours + sum.minutes / 59; sum.minutes %= 60; return sum; } Time Time::operator-(const Time & t) const { Time diff; int tot1, tot2; tot1 = t.minutes + 60 * t.hours; tot2 = minutes + 60 * hours; diff.minutes = (tot2 - tot1) % 60; diff.hours = (tot2 - tot1) / 60; return diff; } Time Time::operator*(double mult) const { Time result; long totalminutes = hours * mult * 60 + minutes * mult; result.hours = totalminutes / 60; result.minutes = totalminutes % 60; return result; } void Time::Show() const { std::cout << hours << " hours, " << minutes << " minutes "; } 重载调用:两种方法都是可以的 total = coding.operator+(fixing); total = coding + fixing; 同时也是支持两个以上对象累加 t4 = t1 + t2 + t3; t4 = t1.operator(t2 + t3) //加法是从左到右结合的,所以显示t1调用 t4 = t1.operator(t2.operator(t3)); //重载限制 1、不能违反运算符原来的句法规则 2、不能创建新运算符 3、不能重载下面的运算符 sizeof ; . 成员运算符; .* 成员指针运算符 ; :: 作用域解析运算符 ?: 条件运算符 ...
关于操作符重载补充[2020年2月19日]
1、上面的操作方式都是函数返回值,对于+=操作函数定义为返回引用
2、友元
//如果要为类重载运算符,并将非类的项作为其第一个操作数,可以用友元函数来反转操作数顺序 Time operator * (double m, const Time & t) { return t * m; } 常用的友元:重载<<运算符 fried ostream & operator<<(ostream & os, const Time & t);//返回是ostream引用 ostream & operator<<(ostream & os, const Time & t) { os << t.hours << " hours, " << t.minutes << " minutes"; return os; } ---------------------------- 重载运算符: 1、成员函数:一个操作数通过this指针隐式地传递,另一个操作数作为函数参数显式地传递 2、对于友元版本来说,两个操作数都作为参数传递
枚举: 枚举介绍
enum weekday {sun, mod, tue, wed, thu, fri, sat}; //枚举值是常亮不可以更改 weekday a, b, c; // 三个枚举变量 a = sun; //a = 0 b = mod; // b = 1 c = tue; // c = 2
随机数的理解
头文件<cstdlib>
rand()是生成随机数的函数,但是随机数的生成时根据一个种子来递推出一系列的数,在使用rand()是默认种子值是1,所以运行两次发现结果一样,故rand()被称为伪随机数,为了是随机数贴近于我们想要的, 需要改变那个种子,所以又有一个srand()函数,参数是种子值,整型,一般用
srand( (int) time(NULL) )
time()
time():此函数会返回从公元 1970 年1 月1 日的UTC 时间从0 时0 分0 秒算起到现在所经过的秒数。如果t 并非空指针的话,此函数也会将返回值存到t 指针所指的内存。
3、类的自动转换和强制类型转换
//当构造函数只接受一个参数时,可以用下面格式初始化类对象 Stonewt incognito = 275; // 直接用一个整数初始化, // 转换函数 operator typeName(); // typeName 是类型 如: operator double (); 1、转换函数必须是类方法 2、转换函数不能指定返回类型 3、转换函数不能有参数 //最好使用显示转换,避免隐式转换 -------------------------------------------- 转换函数与友元函数 Stonewt Stonewt::operator+(const Stonewt & st) const { //成员函数 double pds = pounds + st.pounds; Stonewt sum (pds); return sum; } Stonewt operator+(const Stonewt & st1, const Stonewt & st2) { //友元函数 double pds = st1.pounds + st2.pounds; Stonewt sum(pds); return sum; } 如果提供了Stonewt(double)构造函数: Stonewt jennySt(9,12); double kennyD = 21.12; Stonewt total; total = jennySt + kennyD; // 两种函数都可以 total = kennyD + jennySt; // 成员函数则不可以,因为只有类对象才可以调用成员函数,C++不会试图将pennyD转换成Stonewt对象。将对成员函 数参数进行转换,而不是调用成员函数的对象 经验:将加法定义为友元可以让程序更容易适应自动类型转换。
第十二章 类和动态分配
1、动态内存和类
静态数据成员在类声明中声明,在包含类方法的文件中初始化。初始化时使用作用域运算符来指出静态成员属于的类。但如果静态成员是整型或枚举型const,则可以在类声明中初始化。
1、复制构造函数:新建一个对象并将其初始化为同类的现有的对象时,复制构造函数将被调用。按值传递对象将调用复制构造函数,因此应该用引用传递对象 2、默认复制构造函数功能:逐个复制非静态成员的值, class StringBad { private: char * str; int len; static int num_strings; ... } 使用默认复制构造函数时,复制的不是字符串,而是一个指向字符串的指针。当析构函数被调用时,这将引发问题。析构函数StringBad释放str指针指向的内存 解决方案:定义一个显式复制构造函数 StringBad::StringBad(const StringBad & st) { num_strings++; len = st.len; str = new char [len + 1]; std::strcpy(str, st.str); } //如果类中包含了使用new初始化的指针成员,应当定义一个复制构造函数,以复制指向的数据,而不是指针,这被称为深度复制。
赋值运算符:将已有的对象赋给另一个对象时,将使用重载的赋值运算符。 初始化对象时,并不一定会使用赋值运算符。 与复制构造函数相似,赋值运算符的隐式实现也对成员进行逐个复制。但静态数据成员不受影响。 显式赋值运算符重载: StringBad & StringBad::operator= (const StringBad & st) { if ( this == &st) // 自身赋值直接返回 return *this; delete [] str; // 将原有的空间释放,然后在申请要赋值的空间。 len = str.len; str = new char[len + 1]; std::strcpy(str, st.str); return *this; }
2、在构造函数中使用new时应注意的事项
1、构造函数使用new来初始化指针成员,则应该在析构函数中使用delete 2、new和delete必须相互兼容,new对应delete,new[]对应delete[] 3、如果有多个构造函数,则必须以相同的方式使用new,要么带中括号,要么都不带,因为只有一个析构函数,所有的构造函数都必须和他兼容
3、有关返回对象的说明
1、返回指向const对象的引用 const Vecotr & Max ( const Vector & v1, const Vector & v2) { if (v1.magval() > v2.magval()) return v1; else return v2; } // 返回引用不会调用复制构造函数,效率高;引用指向的对象应该在调用函数执行时存在;v1和v2为const,所以返回类型必须为const 2、返回指向非const对象的引用 比如赋值运算符重载 和 与cout一起使用的<<运算符重载 3、返回对象 Vector Vector::operator+(const Vector & b) const { return Vector(x + b.x, y + b.y); } //在这种情况下,存在调用赋值构造函数来创建被返回的对象开销 4、返回const对象 前面中 force1 + force2 = net 是允许的,复制构造函数将创建一个临时变量来表示返回值,因此表达式force1 + force2的结果为一个临时对象。net被赋给该临时对象,使用完临时对象,将他丢弃。 如果将Vecotor::operator+()的返回类型被声明为const Vector, net = force1 + force2,仍然合法, force1 + force2 = net不合法
对象是不能直接调用私有成员变量的,然而写的运算符重载函数中,通过对象却可以调用私有成员
原因:封装是编译期的概念,是针对类型而非对象的,在类的成员函数中可以访问同类型实例对象的私有成员变量.
5、使用只想对象的指针
1、声明指向类对象的指针: String * glamour; 2、将指针初始化为指向已有的对象: String * first = & saying[0] 3、使用new来初始化指针,这将创建一个新的对象:String * favorite = new String (saying[choice]) 4、对类嗲用相应的类构造函数来初始化新建的对象 String * gleep = new String; String * glop = new String ("my my my"); -------------------------------------------- 再谈定位new运算符: char * buffer = new char[BUF]; JustTesting *pc1, *pc2; pc1 = new (buffer) JustTesting; pc3 = new (buffer + sizeof (JustTesting)) JustTesting("Better Idea", 6); pc3->~JustTesting(); pc1->~JustTesting(); delete [] buffer; //只有显式调用析构函数,否则不会自动调用析构函数; //对于使用定位new运算符创建的对象,应以创建顺序相反的顺序进行删除,原因在于,晚创建的对象可能依赖于早创建的对象。仅当所有的对象都被销毁后,才能释放用于存储这些对象的缓冲区。
第十三章 类继承
继承格式: class RatedPlayer : public TableTennisPlayer { // private: public: RatePlayer (unsigned int r = 0, const string & fn = "", const string & ln = "none", bool ht = false); RatePlayer (unsigned int r = 0, TableTennisPlayer & tp); } // 派生类的构造函数先执行基类的构造函数,然后在执行派生类的构造函数 RatedPlayer::RatedPlayer(unsigned int r, const string & fn, const string & ln, bool ht) : TableTennisPlayer (fn, ln, ht) { rating = r; } RatedPlayer::RatedPlayer(unsigned int r, const TableTennisPlayer & tp) : TableTennisPlayer(tp), rating(r) {} ----------------------------------------------------------------------------- 派生类和基类之间的特殊关系: 1、派生类对象可以使用基类的方法,条件是方法不是私有的 2、基类指针可以在不进行显式类型转换的情况下指向派生类对象; 3、基类引用可以在不进行显式类型转换的情况下引用派生类对象 然而基类指针或者引用只能调用基类的方法,不能调用派生类的方法
基类引用定义的函数或指针参数 可以用于基数对象或者派生对象
void Show(const TableTennisPlayer & rt) // 基类的引用
Show(player1); // 基类对象引用
Show (rplayer1); //派生类对象的引用
---------------------------------------
将基类对象初始化为派生类对象
RatedPlayer olaf1(1840, "Olaf", "Loaf", true);
TableTennisPlay olaf2(olaf1); // 初始化olaf2,使用隐式复制构造函数 TableTennisPlayer (const TableTennisPlayer &) ,形参是基类的引用
2、多态共有继承
1、希望同一个方法在派生类和基类中的行为时不同的。换句话来说,方法的行为应取决于调用该方法的对象。这种复杂的行为成为多态。两种重要的机制可以用于实现多态共有继承机制 1 在派生类中重新定义类的方法 2 使用虚函数 --------------------------------------------------------- 静态联编和静态联编 联编:将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编 静态联编:C++中编译器查看函数参数以及函数名才能确认使用哪个函数,然而C/C++编译器可以再编译过程完成这种联编。 动态联编:编译器必须生成能都在程序运行是选择正确的虚方法的代码这被称为动态联编 -------------------------------------------------------------------------- 指针和引用类型的兼容性 基类指针或者引用转换为派生类指针或者引用 如果不使用显式类型转换是不允许的,对于派生类的新类成员函数不能用于基类 -------------------------------------------------------------- 编译器对非虚方法使用静态联编, 直接根据指针类型调用方法,指针类型在编译时已知;总之编译器对非虚方法只用静态编联 虚函数是根据对象类型调用,只有在运行是才能确认对象类型,编译器生成的代码将在程序执行,将方法关联, 编译器对虚方法使用动态编联。 ---------------------------------------------------------------------- 虚函数的工作原理: 为每个对象添加一个隐藏成员,隐藏成员中保存了指向函数地址数组的指针,该数组被称为虚函数表。虚函数表中存储着类对象进行声明的虚函数地址。 如果派生类重新定义了虚函数,则保存新函数地址,否则保存原始的函数地址,就是基类的; 调用虚函数,程序查看存储在对象中的 虚函数表地址, 每个对象都要增加存储(函数表地址)地址的空间; 每个类还要创建虚函数地址表; 每个函数调用还要执行一项额外的操作,即到表中查找地址 ------------------------------------------------------------------------- 虚函数注意事项: 1、构造函数能不是虚函数 2、析构函数应当是虚函数,这样派生类根据自己的情况来析构 3、友元不能使虚函数,友元不是类成员,只有成员才能使虚函数 4、没有重新定义将使用该函数的基类版本 5、重新定义 不会生成函数两个重载版本,重新定义继承的方法不是重载, 如果重新定义继承的方法,应确保与原来的原型完全相同,但如果返回类型是基类引用或指针,可以修改派生类的引用或指针,这种类型被称为返回类型协变,返回类型随类 类型的变化而变化。 如果基类声明被重载了,则应该在派生类重新定义所有的基类版本,如果只重新定义一个版本,则另两个版本将被隐藏,派生类对象无法使用它们
3、抽象基类
含有未实现的方法:包含纯虚函数的类叫做抽象基类 ----------------------------------------- 继承和动态内存分配: 第一种情况:派生类不使用new; 不需要定义显示析构函数,赋值构造函数和赋值运算符 对于析构函数:编译器定义一个不执行任何操作的默认析构函数,然后 调用基类析构函数; 对于赋值构造函数 和 赋值函数,直接调用基类显式然后,执行自己默认的复制 第二种情况:派生类使用new; 三种都需要 class baseDMA { private: char * label; int rating; public: baseDMA(const char * l = "null", int r = 0); baseDMA(const baseDMA & rs); virtual ~baseDMA(); baseDMA & operator= (const baseDMA & rs); friend std::ostream & operator<<(std::ostream & os, const baseDMA & rs); }; class lacksDMA : public baseDMA { private: enum {COL_LEN = 40 }; char color[COL_LEN]; public: lacksDMA(const char * c = "black", const char * l = "null", int r = 0); lacksDMA(const char * c, const baseDMA & rs); friend std::ostream & operator<<(std::ostream & os, const lacksDMA & rs); }; class hasDMA : public baseDMA { private: char * style; public: hasDMA(const char * s = "none", const char * l = "null", int r = 0); hasDMA(const char * s, const baseDMA & hs); hasDMA(const hasDMA & hs); ~hasDMA(); hasDMA & operator=(const hasDMA & rs); friend std::ostream & operator<<(std::ostream & os, const hasDMA & rs); }; hasDMA & hasDMA::operator=(const hasDMA & hs) { if (this == &hs) return *this; baseDMA::operator=(hs); // 调用hs,然会this指针 delete [] style; style = new char[std::strlen(hs.style) + 1]; std::strcpy(style, hs.style); return *this; } std::ostream & operator<<(std::ostream & os, const hasDMA & hs) { os << (const baseDMA &) hs; // 直接输出基类的对象,然后在输出派生类特有的 os << "Style: " << hs.style << std::endl; return os; }
第十四章 C++中的代码重用
1、valarray类简介
#include <valarray> valarray<int> q_values(8; // 8个整形 valarray<double> weights; valarray<double> v4(gpa, 4); //v4含有4个元素,用gpa前四个初始化 -------------------------- 方法: operator[]() //访问各个元素 size() // 返回包含元素数 sum() // 返回所有元素的总和 max() // 返回最大元素 min() // 返回最小元素
------------------------------------------------------------------
第十五章 友元、异常和其他
1、dynamic_caset<Type *>
第十七章 输入输出和文件
1、
cout是ostream类的对象,其中有方法为put() 和 write(), put方法原型: ostream & put(char) //返回指向调用对象的引用 cout.put('l').put('o'); // 只是允许的 write方法是显示整个字符串,第一个参数提供字符串地址,第二个参数指出要显示多少字符,返回类型ostram& cout.write(taste, len + 5); 即使比taste长度多,也要显示len + 5个
2、
1、修改显示时使用的计数系统 十进制:dec, 十六进制:hex,八进制:oct hex(cout) 或者 cout << hex 等价 2、调整字段宽度 int width(); int width(int i); cout.width(8) // 设置宽度为8,width()方法只影响接下来显示的一个项目,然后字段宽度将恢复为默认值 cout.fill(‘*’)设置填充字符 一直有效 ----------------------------------------------------------------------------- 3、设置浮点数精度 在默认情况下,指的是显示的总位数 cout.precision(2); //对于小数来说显示总位数为2; 4、打印末尾的0和小数点 ios_base类提供了一个setf()函数,能够控制多种格式化特性。这个类还定义了多个常量,可用作该函数的参数 cout.setf(ios_base::showpoint) // 使cout显示末尾的小数点 cout.precision(2); 如果整数部分正好两位,还要显示一个小数点 ------------------------------------- 再谈setf() setf()函数有两个原型。第一个为 fmtflags setf (fmtflags); int temperature = 63; cout.setf(ios_base::showpos); // 在正数前面加+号,只有基数为10时才加 cout << hex; // 十六进制 cout.setf(ios_base::uppercase); // 对于16进制输出, 使用大写字母 cout.setf(ios_base::showbase); //对于输出,使用基数前缀(0,0x) cout << temperature << endl; cout.setf(ios_base::boolalpha); // 输出bool值,可以为true或false cout << true << endl; ----------------------------------------------------------------- 第二个setf()原型接受两个参数,并返回以前的设置 fmtflags setf(fmtflags, fmtflags); cout.setf(ios_base::left, ios_base::adjustfield); //使用左对齐 cout.setf(ios_base::showpos); cout.setf(ios_base::showpoint); cout.precision(3); ios_base::fmtflags old = cout.setf(ios_base::scientific, ios_base::floatfield); // 科学计数法,此时精度是指小数点后面的位数 for (int n = 1; n <= 41; n += 10) { cout.width(4); cout << n << "|"; cout.width(12); cout << sqrt(double (n)) << "| "; } cout.setf(ios_base::internal, ios_base::adjustfield); // 符号或基数前缀左对齐,值右对齐 cout.setf(old, ios_base::floatfield); // 恢复之前,此时精度就是总的位数 cout << "Internal Justification: "; for (int n = 1; n <= 41; n += 10) { cout.width(4); cout << n << "|"; cout.width(12); cout << sqrt(double(n)) << "| "; } cout.setf(ios_base::right, ios_base::adjustfield); cout.setf(ios_base::fixed, ios_base::floatfield); //定点计数法 for (int n = 1; n <= 41; n += 10) { cout.width(4); cout << n << "|"; cout.width(12); cout << sqrt(double(n)) << "| "; }