Chapter 4 Types
1、类型
- 浮点类型:float, double, long double
浮点类型后加f或者F为float,加l或者L为long double,什么也不加默认是double类型
在运算符重载,函数重载这两个点需要注意类型的问题。
- void类型
值集:空集
void* :不限制
2、声明
定义声明+非定义声明
非定义声明:先承认再核实
int x; //全局量(x1)
void f(int x){
//int x; //局部变量冲突定义
x = 1; //x2 = 1
{
int x; //局部量(x3)
x =2; //x3 = 2
::x = 3; //x1 = 3
}
}
::x
全局量
3、枚举类型
枚举值集中的每一个值都是一个已命名的整数常量,这个名字是枚举类型的字面值:
enum keyword{ASM, AUTO, BREAK};//不指定值,从0开始递增
enum summer_month{Jun = 6, Jul = 7, Aug = 8};//指定值,注意值不能重复
Chapter 5 Pointers and Array
1、指针
- 值集:合法的地址集合子集
//1.定义声明
char* pc; //pc:指向char值的指针
int* pi; //pi:指向int值的指针
char** ppc; //指向“指向char值的指针”的指针
/******************************/
/******************************/
int* ap[15]; //ap:由15个指向int值的指针组成的数组
/******************************/
/******************************/
//定义声明
int (*fp)(char*); //定义函数指针
//非定义声明
int* f(char*);
函数指针的定义:
#include<iostream>
using namespace std;
void test(char* p){
cout<<(*p)<<endl;
}
void (*ptr)(char* p) = test;
void (*point)(char* p);
int main()
{
char a = 'C';
point = test;
ptr(&a);
(*ptr)(&a);
point(&a);
return 0;
}
取地址和解引用互为逆操作:&(address of )与*(dereferencing)
2、数组
数组的定义:
数组定义的两种写法
float v[3]; //float[3] v;
char* a[32]; //(char*)[32] a;
int d2[10][20]; //(int[10])[20] d2;
数组大小可以不写:
int v1[] = {1,2,3,4,5}; //int[5] v1;
char v2[] = {'a','b','c',0}; //char[4] v2;
int i,j,k;
int* v3[] = {&i, &j, &k, 0}; //int*[4] v3;
如果给出了size,那么在初始化的时候,元素的个数不能大于size,但是可以小于size。C++自动地给未给出初值的元素赋0
int v1[] = {1,2,3,4,5}; //int[5] v1;
char v2[2] = {'a','b','c',0}; //error!!!
int i,j,k;
int* v3[6] = {&i, &j, &k, 0}; //int*[6] v3 = {&i, &j, &k, 0, 0, 0, 0, 0};
可以将一个常量字符串由一个指针char* p指向,但是不允许修改其中的任何元素,因为该指针指向的是一个const字符数组。(即下面将要说明的:变量的指针会有意无意的修改常量的内容,所以不允许用一个变量的指针指向一个常量,这里比较特殊)
//"this is a string"的类型是const
掌握下面的各个写法最终获得的结果是什么:
int v[] = {1,2,3,4}; //int[4]
int* p1 = v; //隐式转换为int* p1 = &v[0]
int* p2 = &v[0];
int* p3 = &v[4];//允许指针指向数组外的元素,但后果自负
区别*p++
和++p*
之间的区别
void f()
{
int v1[10];
int v2[10];
int i1 = &v[5]-&v1[3];// i1 = 2
int i2 = &v[5]-&v2[3];// result undefined
}
3、数值常量和指针常量
数值常量:const <类型名><常量名> = <初值>
常量指针的声明:数据类型 const * 指针变量或者 const 数据类型 *指针变量。
对于类型是T*时,这个指针不能指向别的内容
指针常量:<指针类型> const <常量名> = <初值>
这里不能修改的是指针所指向的地址,而地址对应的内容可以修改。
指针常量和常量指针的例子:
/*指针常量的例子*/
int a,b;
int * const p;
p = &a;//正确
p = &b;//错误
*p = 20;//正确
/*常量指针的例子*/
int a,b;
int const *p;
p = &a;//正确
p = &b;//正确
*p = 20;//错误
加了[]和解引用*是一样的效果取的都是内容。
区别指针常量和常量指针:
只要是T*开头的就是指针常量,指针常量的地址不能改变,但是地址中的内容可以改变。
除了T*开头的和指针有关的常量,都是常量指针,指针指向的是一个常量,指针的地址可以改变,但是不能通过指针去修改该地址的内容。
变量的变化不会改变指针常量的值,而指针变量的变化会有意或无意修改常量的值,所以不能用一个变量指针去指向一个常量,因为指针可能会修改这个常量的值:
例:
void f4()
{
int a = 1;
const int c = 2;
const int* p1 = &c; // ok
const int* p2 = &a; // ok
int* p3 = &c; // error: initialization of int* with const int*
*p3 = 7; // try to change the value of c
}
修改方法:const int* p3 = &c;
,去掉第9行。
4、引用
The notation X& means reference to X;
void g()
{
int ii = 0;
int& rr = ii; // rr是ii的引用
rr++; // 等价于:ii++;
int* pp = &rr; // pp = = ⅈ *pp = = ii;
}
5、结构
C++中的 struct 和 class 基本是通用的,唯有几个细节不同:
- 使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的。
- class 继承默认是 private 继承,而 struct 继承默认是 public 继承。
- class 可以使用模板,而 struct 不能。
结构中的类型成员不能是它自身。
Chapter 7 Functions and Declarations
1.变量声明
#include<iostream>
using namespace std;
void f(int a)
{
while (a--) {
static int n = 0;
int x = 0;
cout<<"n=="<<n++<<", x== "<<x++<<'\n';
}
}
int main ()
{
f(3);
f(2);
return 0;
}
运行结果如下:
n==0, x== 0
n==1, x== 0
n==2, x== 0
n==3, x== 0
n==4, x== 0
可以看出static变量的声明语句只会被运行一次,但是后续的更改是会不断进行的,即变量的生命期得到延长。
局部变量每次进入作用域都会被初始化一次。
2.参数传递
区分清楚值传递和引用传递;引用传递传递的对象一定是能够取左值的对象。int& ref = 2
这样的定义声明时错误的。
在一个返回值类型为 void 的函数中,可以出现没有返 回值的 return 语句,也可以出现调用另一个返回值类型为void 的函数的 return 语句
void g(int* p);
void h(int* p)
{
if (*p > 0)
return;
return g(p);
}
3.过载函数名
函数名过载,是指它们在同一区域内:
- 相同的函数名
- 不同的参数表(参数数目不同,或类型不同,或顺序不同)。
但是,仅仅是返回值类型不同的两 个函数,编译程序不是按过载函数对待,而是按重复定义了函数进行出错处理。
3.1 二义性
#include<iostream>
using namespace std;
void print(double);
void print(long);
void f()
{
print(1L); // match print(long)
print(1.0); // match print(double)
print(1); // error!
}
int main()
{
return 0;
}
int是能够隐式转换为long和double的,所以造成了二义性
float可以隐式转换为int
- 不在一个作用域里不会发生函数重载
void f(int);
void g()
{
…
void f(double);
f(1); //call f(double)
}
int pow(int, int);
double pow(double, double);
void g( ) {
double d = pow(2.0, 2); // error!
}
- 函数缺省值
注意点:Default Arguments 必须位于参数表的后部,而且中间没有插入非 Default Arguments。
int f(int, int = 0, char* = 0); //ok
int g(int = 0, int = 0, char*); //error
int h(int = 0,int, char* = 0); //error
下面的写法为语法错误,编译器不知道是缺省还是*=,要加一个空格区分
int nasty(char*=0); // syntax error
函数的缺省值在一个作用域中不能重复或者改变,会造成错误。
void f(int x = 7);
void f(int 7); // error: repeat
void f(int x = 8); // error: modifier
void g()
{
void f(int x = 9); // ok: hides the outer one
//…
}
- 使用…代表可能有的更多的参数
这类函数至少应当有一个确定的形参,不能是: T f( … )
#include<iostream>
#include <stdarg.h>
using namespace std;
void test(char c, ...)
{
va_list ap;
va_start(ap, c);
while(1){
int i = va_arg(ap, int);
cout<<i<<endl;
if(i==-1){
break;
}
}
}
int main()
{
test(1,2,3,4,5,6,7,8,-1);
return 0;
}
4.函数指针
函数跳转表的应用:
typedef int (*FP)(char*); // 函数指针类型 FP
int f(char* p);
int g(char* p);
int h(char* p);
const int invalid_fp = -1;
// 利用函数指针构成这一类函数的跳转表
const int table_size = 3;
FP jump_talbe[ ] = { &f, &g, &h };
int driver(int id, char* arg)
{
if (id >= 0 && id < table_size)
return jump_table[id](arg);
return invalid_fp;
}
5.宏
#define MAX(a, b) (a > b) ? a : b
#define MIN(a, b) (a < b) ? a : b
void f()
{
int m = MAX(1, 100); // m = (1 > 100) ? 1 : 100;
double n = MIN(10.123, 6); // n = (10.123 < 6) ? 10.123 : 6;
}
Chapter 9 Source File and Programs
- 不同翻译单元中的同以全局名字,定义声明和非定义声明有相同的类型。
- 定义声明只有一个,其他为extern声明
- 不同翻译单元中能出现相同名字的自定义类型,但是前提是元素得一样,完全一样!!连名字都一样!
不能在.h文件里面出现的:
Chapter 10 Class
1.构造函数
构造函数调用的形式:
A a;//调用默认构造函数
A a(1,2);//隐式调用
A a = A(1,2);//显式调用
A* a = new A(1,2);
当数据成员含有常量类型或者引用类型,那么编译器不会自动提供默认构造函数,除非程序员主动提供构造函数。
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
class Date {
private:
const int a; // const member
const int& r; // const and reference member
};
int main()
{
Date today;// error: no default constructor for Date
retrun 0;
}
2、静态成员函数
只对静态数据成员进行操作的函数:静态成员函数
3、析构函数
desconstructor不允许有形参,因此不过载
重复调用一个对象的析构函数可能引起运行时错误
int main()
{
Table* p = new Table;
Table* q = new Table;
delete p;
delete p; // 可能引起运行时错误
} // 自动调用delete q
Chapter 11 Operator Overloading
1.运算符重载
过载定义操作符 = 、[ ]、( )、->的必须是非静态成员函数,以 保证其第一操作数一定是左值(lvalue);
过载定义操作符时至少有一个操作数是自定义类型的:不能全部都是基本数据类型
- 新类型到基本数据类型的转换
这是单参数构造函数无法完成的,需要重载强制类型转换的运算符,如下:
#include <iostream>
using namespace std;
class complex{
public:
double re;
public:
complex(double re = 0):re(re){
cout<<"constructor~"<<endl;
}
operator double(){
return re;
}
~complex(){
cout<<"desconstructor~"<<endl;
}
};
int main()
{
complex c(4);
cout<<c.re<<endl;
cout<<double(c)<<endl;
cout<<"--------"<<endl;
//delete c2;
return 0;
}
由于返回值类型可知,所以强制类型转化不需要写返回值类型,同时参数表为空,因为是this本身。
2.友元函数
友元函数和成员函数不要同时使用,会产生二义性。
3.explicit
- 拷贝赋值运算符合拷贝构造函数
Chapter 12 Derived Class
一旦一个成员函数在某个类中被定义成虚拟函数,那么在这个类中必须定义这个虚拟函数的实现代码(纯虚拟函数除外)
纯虚函数的定义方法:... function() = 0
class Shape { // abstract class
public:
virtual void rotate(int) = 0; // pure virtual function
virtual void draw() = 0; // pure virtual function
virtual void is_closed() = 0; // pure virtual function
// …
};
注意抽象类不能创建对象!
Shape s; // 错误:不能创建抽象类的对象
Shape *sptr; // 正确
C++的抽象基类常常用来定义共享接口。
如果纯抽象类的子类保持着纯虚函数,没有重写,那么这个派生类仍是抽象类。
#include<stdio.h>
#include<iostream>
using namespace std;
class Date {
protected:
int year, month, day;
public:
Date( int y, int m, int d){
year = y, month = m, day = d;
}
virtual void print() {
cout << year << '-' << month<< '-' << day << endl;
}
};
class ShortDate : public Date {
public:
ShortDate(int y, int m, int d) : Date(y, m, d) {}
void print(){
cout << day << '-' << month << '-' << year << endl;
}
};
void printDate(Date* dp) {
dp->print();
}
class MediumDate : public Date {
public:
MediumDate(int y, int m, int d) : Date(y, m, d) {}
void print(){
switch ( month ){
case 1: cout << "Jan."; break;
case 2: cout << "Feb."; break;
case 3: cout << "Mar."; break;
case 4: cout << "Apr."; break;
case 5: cout << "May."; break;
case 6: cout << "Jun."; break;
case 7: cout << "Jul."; break;
case 8: cout << "Aug."; break;
case 9: cout << "Sep."; break;
case 10: cout << "Oct."; break;
case 11: cout << "Nov."; break;
case 12: cout << "Dec."; break;
default: cout << "Err."; break;
}
cout << " " << day << ", " << year << endl;
}
};
class LongDate : public Date {
public:
LongDate(int y, int m, int d) : Date(y, m, d) {}
void print(){
switch ( month ){
case 1: cout << "January"; break;
case 2: cout << "February"; break;
case 3: cout << "March"; break;
case 4: cout << "April"; break;
case 5: cout << "May"; break;
case 6: cout << "June"; break;
case 7: cout << "July"; break;
case 8: cout << "August"; break;
case 9: cout << "September"; break;
case 10: cout << "October"; break;
case 11: cout << "November"; break;
case 12: cout << "December"; break;
default: cout << "Error"; break;
}
cout << " " << day << ", " << year << endl;
}
};
int main( )
{
Date d (2006, 6, 30);
ShortDate sd (2006, 5, 31);
MediumDate md (2006, 9, 21);
LongDate ld (2006, 9, 21);
printDate(&d); // Display: 2006-6-30
printDate(&sd); // Display: 31-5-2006
printDate(&md); // Display: Sep. 21, 2006
printDate(&ld); // Display: September 21, 2006
return 0;
}
提供的接口不仅仅是成员函数,全局函数也可以方便做接口。
Chapter 13 Templates
1.类模板
将类型作为参数的抽象结构称为类属(generic)
类模板以关键字template开始,后跟参数模板列表:参数模板列表使用typename和class效果相同
#include<stdio.h>
using namespace std;
template<class C> class String{
struct Srep;
Srep* rep;
public:
String();
String(const C*);
String(const String&);
C read(int i)const;
// ...
};
template<class C> struct String<C>::Srep {//如何定义
C* s;
int sz;
int n;
// ...
};
int main()
{
return 0;
}
模板的参数列表中可以声明多个形参:
先声明的可以用来定义统一参数表中的其他参数
template<typename T, T def_val> class count{
//...
}
参数列表中的int类型必须是一个常量:
template<class T, int i> class Buffer{
T v[i];
int size;
public:
Buffer():size(i){}
//
}
void f(int i){
Buffer<int,i> bx;//错误,必须是常量
}
2.函数模板
函数模板写法:
#include<stdio.h>
#include<vector>
using namespace std;
template<class T> void sort(vector<T> &);
void f(vector<int>& vi, vector<double>& vd){
sort(vi);
sort(vd);
}
int main()
{
return 0;
}
template <class T> void sort(vector<T>& v)
{
int n = v.size();
for (int gap = n/2; 0 < gap; gap /= 2){
for (int i = gap; i < n; i++){
for (int j = i-gap; 0 <= j; j -= gap){
if (v[j+gap] < v[j]){
T temp = v[j];
v[j] = v[j+gap];
v[j+gap] = temp;
}
}
}
}
}
函数模板可重载:
template <class T>T max(T x, T y)
{
return(x > y) ? x : y;
}
template <class T> T max(T x, T y, T z)
{
T t;
t = (x > y) ? x : y;
return (t > z) ? t : z;
}
类模板的继承:
template<class T> class Vec : public Vector<T> {
// ...
};
template<class T> class Teacher : public Person {
// ...
};
模板实现的队列:
#include<stdio.h>
#include<iostream>
using namespace std;
template<typename T, int max_size> class Queue
{
private:
T arr[max_size];
int curr_num;
public:
Queue(){
curr_num = 0;
}
bool empty(){
return curr_num<=0;
}
bool full(){
return curr_num>=max_size;
}
void push(const T& v){
if(full()){
cout<<"queue is full!"<<endl;
return;
}
arr[curr_num++] = v;
}
T pop(){
T dummy;
if(empty()){
cout<<"queue if empty!"<<endl;
return dummy;
}
T v = arr[0];
for(int i = 0; i<curr_num; i++){
arr[i] = arr[i+1];
}
curr_num--;
return v;
}
};
int main()
{
const int max_size = 5;
Queue<int, max_size> myQ;
//cout<<myQ.empty()<<endl;
//cout<<myQ.full()<<endl;
for(int i = 0; i < max_size+1; i++){
myQ.push(i);
}
while(!myQ.empty()){
cout<<myQ.pop()<<endl;
}
return 0;
}
Chapter 14 Exception Handling
#include<stdio.h>
#include<iostream>
#include <limits>
using namespace std;
struct Range_error{
int i;
Range_error(int ii):i(ii){
}
};
char to_char(int i){
if (i < numeric_limits<char>::min ()||i > numeric_limits<char>::max ())
throw Range_error (i) ; //出现了异常,报告之,若“有人catch”则跳出, 否则立刻终止运行
return i;
}
int main()
{
try{
to_char(555555555);
}
catch(Range_error e){
cout<<"range_error"<<endl;
}
return 0;
}
捕获任意异常catch(...)
一定要放在catch句组的末尾
下面的写法是错误的!!!
void g() {
try { // … }
catch (…){
// handle every exception
}
catch (std::exception& e) {
// handle any standard library exception
}
catch (std::bad_cast) {
// handle dynamic_castfailure
}
}
nt i = 0; i < max_size+1; i++){
myQ.push(i);
}
while(!myQ.empty()){
cout<<myQ.pop()<<endl;
}
return 0;
}
## Chapter 14 Exception Handling
```cpp
#include<stdio.h>
#include<iostream>
#include <limits>
using namespace std;
struct Range_error{
int i;
Range_error(int ii):i(ii){
}
};
char to_char(int i){
if (i < numeric_limits<char>::min ()||i > numeric_limits<char>::max ())
throw Range_error (i) ; //出现了异常,报告之,若“有人catch”则跳出, 否则立刻终止运行
return i;
}
int main()
{
try{
to_char(555555555);
}
catch(Range_error e){
cout<<"range_error"<<endl;
}
return 0;
}
捕获任意异常catch(...)
一定要放在catch句组的末尾
下面的写法是错误的!!!
void g() {
try { // … }
catch (…){
// handle every exception
}
catch (std::exception& e) {
// handle any standard library exception
}
catch (std::bad_cast) {
// handle dynamic_castfailure
}
}