第一章 文件头及声明
关于extern
使用extern 声明而不定义,它是说明变量定义在程序其他地方
全局不初始化的extern int i; 是声明不定义;只要声明并且有初始化式,那么就是定义;带有extern且有初始化的声明(也是定义),比如extern float fval =2.34; 这种必须放在函数外面,否则出错
文件B要访问另外一个文件A中定义的变量,那么在B中必须先extern声明一下,并且不需要include A。另外,A中定义的变量一定是全局变量。
Extern C
const常量在函数外定义默认是文件级,别人不可访问。要想成为程序级(被其他文件访问)必须要加extern。A.pp中extern const int ival=23;而在B.cpp中extern const int ival;声明一下就可以使用(声明时const不是必须,但最好加上)。另外,const常量在声明的时候必须初始化,如果是使用常量表达式初始化,最好放在头文件去定义(头文件特殊的可以放定义的三个之一)。否则只能放在源文件中定义,并加上extern以能被多个文件共享。
struct MSGMAP_ENTRY {
UINT nMessage;
void (*pfn)(HWND, UINT, WPARAM,LPARAM);
};
struct MSGMAP_ENTRY _messageEntres[] = {
WM_LBUTTONDOWN, OnLButtonDown,
WM_RBUTTONDOWN, OnRButtonDown,
WM_PAINT, OnPaint,
WM_DESTROY, OnDestroy
};
关于结构体的说明:
上面定义了一种结构体类型后,后面要定义数据类型时就要struct MSGMAP_ENTRY 变量名。 蓝色部分就是当变量类型使用
第二章 变量和基本类型
- 只有内置类型存在字面值,没有类类型或标准库类型的字面值(可以这样理解,是内置类型组成了其他类型)。
C++中有整型字面值,浮点字面值,布尔字面值和字符字面值,字符串字面值,转义序列,多行字面值
- 下面
a) ‘数字1’===0x31 (49)
b) ‘A’===0x41 (65)
c) ‘a’=====0x61(97)
- 由空格,制表,换行连接的字符串字面值可以连接成一个新的字符串
但是连接字符串与宽字符串的结果就不可预料了
代码续行 \后面不能有空格,只能是回车,接下来的一行要从头开始,没有缩进 字符串换行要加/ ===》小线倾斜的方向不一样
- 1024f有错,整数后面不能有f
- 2.34UL有错,浮点数后面不能有U
- 标识符不能以数字开头
- 直接初始化与复制初始化 int ival = 123; int ival(34);
- int ival = 09; 错! 八进制数,不能有大于等于8的数字
- 函数外定义的变量初始化为0,函数内的变量不进行初始化(可能是一些无意义的值但是合法的值,所以造成错,所有编译器难以发现所有这类错)
所以,建议每个内置类型对象都要初始化
在函数外定义的类类型对象使用默认构造函数初始化,对于没有默认构造函数的类型,要显式初始化,即使用带参构造函数
- 初始化不是赋值。初始化要分配空间并给初值,而赋值则是要替换当前值
- 声明与定义的区别
a) 定义要分配空间,一个变量程序中只能定义一次
b) 声明用于向程序表明自己的名字和类型,程序中可以出现多次;定义也是声明,定义的时候也声明了它的名字和类型
c) 使用extern 声明而不定义,它是说明变量定义在程序其他地方
函数内或外int i; 都是定义(都分配空间,但函数内没有初始化为0) // 不能放在头文件中。
d) 全局不初始化的extern int i; 是声明不定义;只要声明并且有初始化式,那么就是定义;带有extern且有初始化的声明(也是定义),比如extern float fval = 2.34; 这种必须放在函数外面,否则出错
e) 文件B要访问另外一个文件A中定义的变量,那么在B中必须先extern声明一下,并且不需要include A。另外,A中定义的变量一定是全局变量。
f) 在正式编写程序语句前定义的一些全局变量或局部变量,在C中为声明,C++中为定义 ( int a;//在标C中为声明,是不可执行语句;在C++中为定义)
- 定义在函数外的变量有全局作用域
- 局部同名变量屏蔽了上层作用域的变量,烂程序,不易读。在局部中要想使用全局中的同名变量在变量前使用域作用符::
- 在内建数据类型的情况下,++i与i++效率没区别,在自定义数据类型的情况下++i的效率高
- c++中,定义和声明可以放在任何可以放语句的位置,所以,通常把一个对象定义在首次使用它的地方是一个很好的办法
- const int pi = 3.14; 采用const易维护(在多次出现3.14的地方使用const变量来代替)
再比如:
const int max = 23;
for (int i = 0; i != max; ++i)
标准C++中,for中定义的i只在语句作用域中(for 语句),出了for就不可见了
- 全局变量的程序级与文件级, const全局变量
a) 普通变量在函数外定义就是程序级,别的文件要使用只先extern声明一下就行。并且不用include变量定义的文件
b) const常量在函数外定义默认是文件级,别人不可访问。要想成为程序级(被其他文件访问)必须要加extern。A.pp中extern const int ival=23;而在B.cpp中extern const int ival;声明一下就可以使用(声明时const不是必须,但最好加上)
c) const常量在声明的时候必须初始化,如果是使用常量表达式初始化,最好放在头文件去定义(头文件特殊的可以放定义的三个之一)。否则只能放在源文件中定义,并加上extern以能被多个文件共享。
- 关于const引用与非const引用与它们所绑定的对象的关系:(const引用是指向const对象的引用,非const引用是指向非const对象的引用)
a) 非const引用类型必须要与它所引用的变量的类型一致(非const引用只能绑定到与该引用同类型的对象,而不能是右值)
int ival =12;
int &ref = dval 或34; //error 初始化的时候只能绑定到同类型的对象(不能是右值)。 非初始化的时候可以被赋值,引用与其绑定的对象的值都会改变
b) const引用可以绑定到不同但相关(即可以转化)的类型的对象(可以是非const对象)或右值
比如[const] double dval= 3.14; // 这里要不要const都行
const int &refVal = dval; // warning,编译器会中间把3.14转成int temp的3,然后再给了refVal,这里编译不出错,但是会给出警告
constint &refVal2 = 2.33; // warning,这里const不可少,不然不使用右值
refVal2 = dval; // error, const引用的值不可改变
- enum color {red, blue=4, white};其中white是5
枚举成员是常量而不能改变,所以初始化时要用常量表达式,const常量与整形字面值都是常量表达式
扩展color black = red; // 必须使用里面定义的来作为右值进行初始化
color pink = 3; // error
- 设计类:从操作开始设计类。先定义接口,可以决定需要哪些数据以及是否需要私有函数来支撑公有函数
- class与struct定义类仅仅影响的是默认访问级别,struct为public,class为private
- c++支持分别编译separatecompilation,头文件和源文件。将main放在其他的源文件中.
头文件中有类定义,extern变量声明,变量和函数声明,带来的莫大的好处,一是统一性:保证所有文件使用的同一声明,二是易维护:需要修改时,只改头文件就行了,易维护
设计头文件时注意:声明最好放在一起。编译头文件需要一定的时间。有些C++编译器支持预编译头文件,需要查手册。
头文件用于声明,而不是定义——有三个例外:类定义,用常量表达式初始化的const变量和inline函数的定义。它们可以在多个源文件中定义(相当于头文件被include到了多个源文件中),只要在多个源文件中的定义时相同的。
解释:允许在头文件中定义类和inline函数是因为编译器需要它们的定义来产生代码,对于类类型需要知道类对象的数据成员和操作才能分配空间。但是对于单个源文件A.cpp为了避免多重包含,在定义时须加#ifndef与#endif(头文件应该有保护符,会被其他文件包含)
如果将inline函数的定义放在某源文件中,那么其他源文件将永远访问不到
允许在头文件中定义const常量的原因:const常量是文件级作用域的,多个文件包含它互不影响,不算是重复定义。所以可以在头文件中定义。多个文件共享const变量的前提是必须保证多个文件使用的是相同的名称和值,将它放在头文件中,谁需要的时候就include它就行,安全方便。
另外要注意:在实际中,源文件里大多编译器只是像宏替换一样的使用常量表达式来替换const变量,而没有分配空间来存储用常量表达式初始化的const变量。
如果const变量不是用常量表达式初始化的,这就不应该放在头文件中定义。此时它应该放在源文件中定义并初始化,加上extern以使它能够被多个文件共享。
- //Page58页小字部分:编译和链接多个源文件组成的程序
- 关于inline函数
inline函数目的是:为了提高函数的执行效率(速度)。以目标代码的增加为代价来换取时间的节省。
非内联函数调用有栈内在的管理,包括栈创建和释放的开销。函数调用函数调用前保护好现场,返回后恢复现场,并按原来保存的地址继续执行。对于短小且频繁执行的函数,将影响程序的整体性能
在C中可以用#define,编译器用复制宏代码的方式取代函数调用,但没有参数类型检查。
有两点特点注意的:
(1) 内联函数体中,不能有循环语句、if语句或switch语句,否则,函数定义时即使有inline关键字,编译器也会把该函数作为非内联函数处理。
(2) 内联函数要在函数被调用之前定义,否则内联失效。将它的定义放在头文件中就很好的可以做到这点,由于是在源文件中先include,再后面调用的,就保证了调用之前定义
(3)关键字inline必须与函数定义体放在一起才能使函数真正内联,仅把inline放在函数声明的前面不起任何作用。因为inline是一种用于定义的关键字,不是一种用于声明的关键字。(根据高质量C/C++指南,声明前不应该加,因为声明与定义不可混为一谈)
- 复合类型compoundtype,引用,数组,指针
第三章 标准库类型
1.#include
usingstd::string;
using std::cout;
2.头文件中定义确定需要的东西
- 注意区分string类型与字符串字面值
- 在Windows下用命令行要编译和运行.cpp文件
打开vs2005命令提示
cl /EHsc simple.cpp 生成exe文件//EHsc 命令行选项指示编译器启用 C++ 异常处理
若要运行 simple.exe 程序,请键入 simple 并按 Enter
要加入程序变量: simple < data\book_sales
vs2005中同样也可以加入程序变量,就在命令行参数里
- a) cin.getline(char*, int, char) // 是istream流
// 可以接收空格并输出,它的参数有三个,第三个是结束符,默认为’\n’
char ch[20];
cin.getline(ch,5);
cout<< ch << endl; // 输入abcdefg,输出abcd最后一个为‘\0‘
//这个\0是自动加上的,所以可以cout << ch;
char ch[20];
cin.getline(ch,5,‘a‘); // 遇到a结束,并加上\0
cout<< ch << endl; //输入xyamnop,输出xy
b) getline(cin, str), 须加#include // 是string流
#include <string> istream& getline( istream& is, string& s, char delimiter = ‘\n‘ ); // delimiter分隔符 接收一行字符串,可以接收空格并输出,遇到回车返回,并丢掉换行符。不忽略行开头的换行符,如果第一字符是回车,那么str将是空string
逐行输出:
string str;
while(getline(cin, str)) {
cout << line << endl; // 由于不含换行符,需要endl刷新输出缓冲区
}
逐词输出:
string str;
while (cin>> str) {
cout << str << endl;
}
c) cin.get()吃掉没有用的字符,读到回车才退出
d) char ch1[100],ch2[100];
cin >> ch1 >> ch2; //接收字符串,忽略有效字符前的空白字符,以空白符(空格,TAB,回车)作为输入结束
而getline(cin,str) 不忽略开头的空白字符,读取字符直到遇到换行符,读取终止并丢掉换行符
e) gets(str) 接收一个字符串,可以接收空格并输出。加#include
与getline(cin,str)类似
f) getchar()无参数,须加#include ,是C中的函数,尽量少用或不用
- str.size()的实现(返回有效字符的个数,不含最后的空字符,它的类型是string::size_type)
constchar *st = “The expense of spirit\n”;
int len = 0;
while (*st) { ++len; ++st; }
str.size的应用
for (string::size_type ix = 0; ix !=str1.size(); ++ix)
- string对象可以==, >=, <= !=操作
- string的赋值操作:str1 = str2;须先把str1的内存释放,然后再分配能存放副本大小的空间,再复制过来
- string s2 = “hello” + s1; //error,开头必须是变量
-
include p77页
isalnum(c) 字母数字
isalpha(c) 字母
iscntrl 控制字符
isdigit 数字
isgraph 不是空格但是可以打印
islower 小写
isprint 可打印
ispunct 标点
isspace 空格
isupper 大写
isxdigit 十六进制数
tolower 变小写
toupper
- p78 使用C标准库头文件,使用#include 不要用#include
第四章 数组和指针
- int arr[32]; // 维数必须是字面值,枚举常量,常量表达式初始化的const变量
上面这句话在函数外,则初始化为0
若定义在函数内,没有初始化,但分配了空间
int arr[5] = {1,2,3}; // 剩下的初始化0,若是类类型就使用默认初始化构造函数, java中不行,不能写维数的
vector<int> ivec = {1,2,3}; // error,vector没有这样的初始化
- 数组不能直接复制和赋值
- 数组下标越界导致:buffer overflow
- 比较两个vector,先比较长度
- 现代C++使用vector替换数组,使用string替换C风格字符串
- 关于指针:
a) string* s1, s2看上去很不好,所以尽量将与变量名放在一块
b) 避免使用未初始化的指针
C++没有办法检查出未初始化的指针,使用它可能会导致基础数据
如果要指向的对象还没有存在,那么就先不要定义这个指针。如果要定义,就=0或NULL(#include )
预处理器变量NULL不是在std命名空间中定义的,所以不是std::NULL
c) void *
void* 这不叫空指针,这叫无确切类型指针.这个指针指向一块内存,却没有告诉程序该用何种方式来解释这片内存.所以这种类型的指针不能直接进行取内容的操作.必须先转成别的类型的指针才可以把内容解释出来;
‘\0’不是空指针所指的内容,而是表示一个字符串的结尾,不是NULL;
真正的空指针是说,这个指针没有指向一块有意义的内存,比如说:
char* p;
这里这个p就叫空指针.我们并未让它指向任意地点,又或者 char* p = NULL;这里这个p也叫空指针,因为它指向NULL 也就是0,注意是整数0,不是’\0’
一个空指针我们也无法对它进行取内容操作.
空指针只有在真正指向了一块有意义的内存后,我们才能对它取内容.也就是说要这样p = “hello world!”; 这时p就不是空指针了
后面会看到:不能使用void*指向const对象,必须使用const void *类型的指针保存const对象的地址
void *p; 只支持以下操作:
与另一指针比较
作参数或函数返回值
给另一个void *赋值
- 指针与引用的比较:
引用定义时必须初始化!
引用被赋值后改变的是所引用的对象
指针被赋值后将会指向另一个变量的地址,不专一
- int arr[5];
int *p = arr;
int *p2 = p + 2;// 那么,p的算术运算要求原 指针与计算出的新指针都要指向同一数组的元素,结果范围必须在[0,5],//注意这里5也算合法结果
注意:C++允许计算数组或对象的超出末端的地址,但不允许对此地址进行解引用操作
可以把指针看成是数组的迭代器
ptrdiff_tn = p1 – p2; // #include
// 与机器相关:size_t是unsigned, ptrdiff_t是signed
注意,ptrdiff_t只保证指向同一数组的两个指针间的距离,若是指向不同的指针,那么error
t4_17:
p1 += p2– p1; 若p1与p2指向同一数组那么始终合法,让p1指向p2所指向的地址
- 关于const指针
a) 指向const对象的指针
const double pi = 3.14; // 不能用普通指针来指向它 但是相反地,可以用const double*的指针指向非const的变量
const double *cptr = 0或NULL; // 这里const限定的是所指向的对象,而不是cptr本身,所以定义的时候可以不初始化, 蓝const是必须的,防止通过cptr来改变所指对象的值
cptr = pi; // 也可以在定义的时候初始化
指向const对象的指针可以指向非const对象
注意:不能保证cptr指向的对象的值一定不可修改!它完全有可能指向一个非const对象,将它赋给普通指针就可以改
double dval = 2.34; // 非const
cptr = &dval; //可以指向一个非const对象,但仍不可通过cptr来修改
const void cpv = pi; // ok ,不能使用void指向const对象,必须使用const void *类型的指针保存const对象的地址
t5_33: 将一个指向const对象的指针成void *
const string *ps;
void *pv;
pv = (void *)ps; // error!!!应该按下面写
pv = static_cast
第五章 表达式
- 指针不能转化为浮点类型
- short [-32768,32767]
unsigned short: [0, 65535]
int: [-21.4亿, 21.4亿]
- -21 % -8 值为 -5
- 对于位操作,由于系统不能确保如何处理符号位,所以建议使用unsigned数!
左移补0
unsigned char ch = 0277;
ch = ~ch; 全部取反 flip操作
ch << 1; // 左移,右边补0
//右移左边补0; 右移个数要小于8(操作数的位数)
对于signed,右移补0还是符号位根据机器而定
bitset优于整数数据的低级直接位操作
bitset_quiz1.set(27);
int_quiz1 |= 1UL << 27;
bitset_quiz1.reset(27);
int_quiz1 &= ~(1UL << 27);
<<优先级高于<
所以 cout << (10 < 32); // ok
cout << 10 < 32; // error
- i + j = k; // error
int i;
const int ci = i; // ok, 将来ci无意义
- int a; int *p;
a = p = 0; // error 不能将指针类型赋给int
- a += b; a计算了一次, a = a + b; a计算了两次
- 使用++i而不用i++:
工作量少啊,后置操作要先保存操作数原来的值,以便返回未加1之前的值作为操作的结果。
- .>* 取成员操作大于解引用操作
(*pstu).name = “leiming”;
pstu->name = “leiming32”;
++>* ++也大于解引用操作
iter++ 相当于 (iter++)
- t5_18:定义一个string指针的vector,输入内容,然后输出
vector
第六章 语句
- if (int i = 3) // i转化成bool值true; 迄今为止,在所有用过的类型中,IO类型可以用作条件,vector和string类型一般不可用作条件
上面,出了if后,i不可访问,下面出了while之后同样不可访问
while (int i = fun(j)) // 每次循环要执行i的创建和撤销过程
- 每个case不一定非要另起一行
case 1: case 2: case 3: case 4: case 5:
++i;
break;
case值必须是整型常量表达式
有两个case值相同则编译错
良好的程序要定义default: 即使没有什么要做的,;空语句也行
switch内部的变量定义:规定只能在最后一个case之后或default之后定义变量,要为某个case定义变量,那么要加{}。这样是为保证这个变量在使用前被定义一初始化(若直接执行后面case,且用到了这个变量 ,由于作用域可见,但未定义就使用了)
- t6_12.cpp 寻找出现次数最多的单词,要求程序从一个文件中读入
- do{}while();以;结束
- 在设计良好的程序中, 异常是程序错误处理的一部分
异常是运行时出现的异常
include
try {
if (true)
throwruntime_error(“error!”);
} catch (runtime_error err) {
cout << err.what() << endl;
}
先在当前代码中找catch,若没有匹配的,则一上层调用函数中寻找,若仍没找到,则一直找下去,最终由#include 中定义的terminate终止程序执行
若程序在没有try catch的地方出现异常,则由terminate自动终止程序的执行
bitset的to_ulong操作,若提供的位数大于unsigned long的长度32位时,就异常overflow_error,查看t6_23
bitset<33> ib;
ib[ib.size()-1] = 1; // 最高位是1
try{
ib.to_ulong();
}catch (overflow_error err) {
cout<< "overflow" << endl;
cout<< err.what() << endl; // what返回const chat*
}catch (runtime_error err) {
cout<< "runtimeerror" << endl;
cout << err.what() << endl;
}
- 异常类 #include
exception // 最常见异常类
runtime_error // 下面几个运行时错
range_error // 生成的结果超出了有意义的值域范围
overflow_error // 上溢
underflow_error // 下溢
logic_error // 可在运行前检测到的问题,下面几个逻辑错
domain_error // 参数的结果值不存在
invalid_argument // 不合适的参数
length_error //超出该类型最大长度
out_of_range //使用一个超出有效范围的值
include
bad_alloc
include
bad_cast
以上红色的三个只有默认构造函数,不使用string初始化
- 使用预处理器编写调试代码
ifndef NDEBUG
cerr << “oh,shit!” << endl;
endif
很多编译器执行时
$ CC –DNDEBUG main.C
相当于在main.c开头提供#define NDEBUG预处理命令
- 预处理宏assert #include
assert(expr)与NDEBUG配合使用的
只要NDEBUG没有定义,则执行assert,若为false则输出信息并终止程序的执行
测试不可能发生的条件
异常是处理预期要发生的错误,而断言是测试不可能发生的事情
一旦开发和测试完成,程序就已经建立好,并定义了NDEBUG。成品代码中,assert不执行,因此没有运行时代价,没有运行时检查。
assert仅用于检查确定不可能的条件,只对程序的调试有帮助,不能用来代替运行时的逻辑检查logic_error,也不能代替对程序可能产生的错误runtime_error的检查
- 百度百科中:
编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设,可以将断言看作是异常处理的一种高级形式。断言表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真。
使用断言可以创建更稳定,品质更好且易于除错的代码。当需要在一个值为FALSE时中断当前操作的话,可以使用断言。单元测试必须使用断言
除了类型检查和单元测试外,断言还提供了一种确定各种特性是否在程序中得到维护的极好的方法。
使用断言使我们向按契约式设计更近了一步。
我这样理解,一座桥,有护栏,人们正常行走在上面,
assert(有个人翻越护栏跳进河)
assert(桥断了) // 这些都是我们认为不可能的,假设真的
上面是不可能发生的情况;
try {
if (护栏坏掉)
} catch(runtime_error err) {
cout << err.what() << endl;
}
while(cin >> s) {
assert(cin); // 多余的
}
string s;
while(cin >> s && s != sought) {}
assert(cin);
解释:在打开调试器的情况下,该循环从标准输入读入一系列单词,直到读入的单词与sought相等或遇到文件结束符。如果遇到EOF,则执行assert并终止程序。
如果关闭调试器,则assert不做任何操作
第七章 函数
- 最大公约数
int a,b;
while (b) {
inttemp = b;
b =a % b;
a =temp;
}
return a;
- 函数必须指定返回类型
- 指针形参,不改变实参指针值,但可以改变它们共同指向的对象
- 形参使用引用可以避免复制。有些类不能复制。应该将不需要修改的引用形参定义为const引用。普通的非const引用形参不能用const对象初始化,也不能用字面值或产生右值的表达式,以及需要进行类型转换的对象作为实参初始化,只能与完全同类型(short给int引用会出错)的非const对象关联!
- void ptrswap(int &p1, int&p2) // 传值(指针值)调用,函数里面交换两个指针的值,这样的话,实参指针也交换了值
- 什么时候应该将形参定义为引用:修改实参,大型对象不能复制,无法复制的类
- 通常函数不应该有vector或其他容器类型的形参,传递迭代器就可以了(模板实现)
- 数组形参
由于不能复制数组,所以无法编写使用数组类型形参的函数。数组可以自动转化成指针 ,所以通常通过操纵指向数组中的元素的指针来处理数组。
以下三个等价:都看作是指向数组元素的指针
void fun(int*)// 最好
void fun(int[]) // ok
void fun(int[10])
数组实参
数组实参若以非引用形式传递,则会悄悄的转成指针,非引用类型的形参会初始化为相应的的副本,实参是第一个元素的地址,而形参复制的是这个地址的副本,操作的是这个副本,所以不会修改实参指针的值,但是可以修改它们共同指向的对象内容。
所以,数组非引用实参是指针的值传递!
如果不修改数组元素(保护数组)时,那么应该定义为指向const对象的指针
void fun(const int*)
数组引用形参
void printValues(int (&arr)[10]) { ...... } // 括号是必须的
这个时候,数组实参不会转成指针了,而是传递数组引用本身
必须写大小,实参的大小必须与10匹配
t16_16,模板实现,允许传递指向任意大小的引用形参
多维数组的传递
void printValues(int (*matrix)[23], int rowSize) { .... } void printValues(int matrix[][23], introwSize); // 实际上是指针
- t7_15
define NDEBUG // 必须放在第一行
include
using namespace std;
//#define NDEBUG // 移到第一行去
int main(int argc, char** argv) {
assert(argc== 33);
cout<< "sum=" << (atof(argv[1]) + atof(argv[2])) <<endl;
system("pause");
return0;
}
- 非void函数必须返回的特殊情况,main函数最后的reutn 0不是必须的
main函数不能递归,不能调用自身
main函数不能重载
include定义了两个预处理器变量,说明程序执行成功或失败:
EXIT_FAILURE
EXIT_SUCCESS
- 函数返回值
返回值用于初始化在调用函数处创建的临时对象temporary object
如果返回非引用类型,使用局部对象来初始化临时对象
如果返回引用类型,则不要返回局部对象的引用,
也不要返回局部对象的指针
返回引用的函数:没有复制返回值,返回的是对象本身
const string &shorterStr(const string&, conststring&);
如果不希望返回的对象被修改,加const(不能作为左值了)
引用返回左值:返回引用的函数返回一个左值
inlinensaddr_t& pkt_src() { return pkt_src_; }
main函数中:
pkt_src()= 实参值;
如果不希望引用值被修改,返回值应该声明为const:
inline cosnt nsaddr_t& pkt_src() { return pkt_src_; }
该引用是被返回元素的同义词
- 考题
int &max(int a, int b) { // 返回引用.cpp
returna > b ? a : b;
}
int main() {
inti = 1, j = 2;
max(i,j)= 3;
cout<< "i=" << i << ",j=" << j <<endl; // i=1,j=2
// 因为形参不是引用,不会改变j的值,改变的是局部b的值
system("pause");
return0;
}
- 函数声明
变量和函数都声明在头文件中,定义在源文件中
源文件包含声明函数的头文件,进行匹配检查
声明在头文件中的好处是确保在所有文件中声明一致,修改方便
- 默认实参,:如果有一个形参有默认实参,那么它后面的所有形参都要有
使用最频繁使用的排在最后
默认实参可以是表达式或有返回值的函数调用
在头文件中声明默认实参(别人包含后才是有效的),在源文件中定义的时候就不要写默认实参了,否则出错,因规定一个文件中默认实参只能声明一次(定义也是声明呀)
- 自动对象
只有当定义它的函数调用时才存在的对象
一般指形参和局部变量,它们在定义它们的语句块结束时撤销
静态局部对象
在第一次经过它的定义语句时创建,程序结束时撤销
size_t count() {
static size_t c = 0; // 只执行一次
return++c;
}
int main() {
for(size_t ix = 0; ix != 20; ++ix) {
cout<< count() << “ ”;
}
return 0;
}
输出1,2,3,4,5…..
- const string &shorterStr(const string&,const string&);
定义它的好处:易读,易改,保证统一,code reuse
坏处,慢!怎么办 inline!!!!
对于内联函数,编译器要展开代码,仅看到原型是不够的,
内联函数应该在头文件中定义,在头文件中加入或修改内联函数时,使用该头文件的所有源文件必须重新编译!
- 成员函数
每个成员函数都有一个this形参,是调用该函数的对象的地址
对象实例调用自己的成员函数,将自己传给这个成员函数与之进行绑定
this指针是隐含的,不能明式出现在形参表中
常成员函数bool same_isbn(constSales_item&)const;
total.same_isbn(trans);调用时,则这个this是指向total的const Sales_item*类型的指针, 是个指向const对象的指针,所以这个函数不能改变数据成员
const对象、指向const对象的指针或引用只能用于调用其const成员函数!!反过来,非const的这帮东西可以调用const的成员函数(并非必须是const的对象才能调用)
换句话:const对象不能调用非const成员函数
对于成员函数,声明与定义必须一致,如果要const那么两者都要同时有const
- 对象如果在全局作用域或定义为局部静态对象,那么成员初始化为0,如果对象在局部作用域中定义那么没有初始化
如果类成员有内置类型或复合类型(用其他定义的类型,如引用,指针,数组,容器)成员,那么最好自己定义默认构造函数,不要合成的
- 作业t7_31 Sales_item.h
- 函数返回类型,最右边形参具有默认实参,以及const的非引用形参不能作为重载的区别标志
int fuc1(int*);
int fuc1(const int*); //重复声明 ,没有error
//对于第二个,实参即可以是const int*也可以是非const int*的普通指针,所以没区别,都是值传递,对副本操作
补充,若形参是const &int那么区别了,是重载!此时,若实参是const对象则调用带有const的那个函数,若调用另一个是需要转换的故而不优,由此区分!若实参是非const对象,则两个都可以调用,优先选择调用不带const的那个(调用带有const的也需要转换);指针也类似的情况(如果实参是const对象,则调用带有const*类型形参的函数,否则如果实参不是const对象,将调用带有普通指针形参的函数)
f(int*)
f(const int*)// 可以重载
而下面的
f(int*)
f(int*const) //不能重载(值传递,副本传递) // 这里是const指针,只有指向const对象的指针作形参才能实现重载
再补充:仅当形参是引用或指针时,形参是否为const才有影响
int fuc2(int, int);
int fuc2(int, int b = 2); //重复声明
p229 建议:何时不重载函数名
- 函数重载的前提是函数彼此间作用域相同,否则就是屏蔽关系
局部声明函数是个坏习惯,一般都有要声明在头文件中
C++中,名字查找发生在类型检查之前,所以,在局部作用域内找到名字对应的函数后,不再去外层找了,即使类型不是最合适的。
重载确定
找与实参最佳match
void f(int)
void f(double, double = 3.21)
调用f(3.2); 将选择下面那个,因为精确匹配优于需要类型转换的匹配。编译器将自动将默认实参提供给被忽略的实参,因此这个调用函数拥有的实参可能比显式给出的多
void f(int, int);
void f(double,double)
调用f(2, 3.24);有二义性
设计形参尽量避免实参类型转换
注意,较小的整型提升为int型。小的提升为int,优于形式上合适的char类型的匹配!
void f(int)
void f(short)
ff(‘a’) // match上面第一个
还有,类型提升优于其他标准转换!
例如:char到double优于到unsigned char的转换
void f(unsigned char) // 若去掉unsigned则选择第一个
void f(double)
调用f(‘a’) //二义
参数匹配与枚举类型
枚举类型的对象只能用同种枚举类型的另一对象或一个枚举成员进行初始化
整数对象即使有与枚举元素相同的值也不能用于调用期望获得枚举类型实参的函数,但是可以将枚举值传递给整型形参(129的时候提升为int,而不是unsigned char)
- 函数指针:bool (*pf)(int); //指向函数的指针
而指针函数:bool *pf(int); // 返回值是指向bool指针
使用typedef简化函数指针
typedefbool (*pf)(int); //pf是一个种指向函数的指针的名字,它所指向的函数有一个int参数,返回值是bool类型
函数指针的初始化和赋值: (三种,0、函数名、&函数名)
第一种:pf fuc1 = 0;
//等于bool (*fuc1)(int)= 0; // 蓝色部分就是类型
第二种:使用函数名
注意:bool setAge(int); 其中setAge除了用于函数调用外,其他任何使用都将解释为指针: bool(*)(int);
pf fuc2 = setAge; // 使用函数名进行初始化
fuc1 = fuc2; //或赋值
上面直接使用函数名setAge等于在函数名上取地址操作
pffuc2 = setAge;
pffuc2 = &setAge; // 两个等价
而使用 pf fuc3(3); // 直接初始化是错的
指向不同类型函数的指针不存在转换
可以通过函数指针调用函数
fuc2 = setAge;
调用:fuc2(32); // 前提是这个函数指针已经初始化,若是0或未初始化不行
函数指针作形参
voidsamplefuction(const string&, bool(int));
//函数类型的形参,对应实参将被自动转换为相应的函数指针
voidsamplefuction(const string&, bool (*)(int));
//函数指针类型的形参,与上面声明等价
返回类型是指向函数的指针(返回类型可以是函数指针类型,但不可以是函数类型) 后补充:函数ff的返回类型是一个指向”外围”函数的指针
bool (*ff(int))(int*, int) // 蓝色部分是类型 这句话用在这里是在声明ff这个函数
ff(int)函数返回一个函数指针,它的类型是bool (*)(int*, int)
使用typedef使用该定义更加简明易懂
typedefbool (*PF)(int*, int)
PFff(int); // 声明ff这个函数
举例:
typedef int func(int*, int);
void f1(func); // ok,函数类型可以作形参,对应实参将自动转成函数指针
func f2(int); // error!! 返回类型不能是函数类型,不能自动转成函数指针
func *f3(int); // ok, 返回类型是函数指针
指向重载函数的指针
必须精确匹配
extern void ff(double);
extern void ff(int); // ok,重载
void (*pf)(double)= &ff; // ok, 精确匹配,对应第一个
int(*pf)(double) = &ff; // error, 返回值不匹配
第八章 IO
- #include
include // 从iostream继承
include // 从iostream继承
- 国际字符支持
char有相应的wchar_t
wostream wistream wiostream
wifstream wofstream wfstream
wistringstream wostingstream wstringstream
- IO对象不能复制或赋值
不以放在vector或其他容器中
不能作为形参,必须是引用或指针(返回值也要是引用)
形参也是非const的,因为IO对象的读写会改变状态
- 条件状态:
strm::iostate
strm::good 0 000
strm::badit 1 001 系统级
strm::failbit 2 010 可恢复
strm::eofbit 3 011
s.eof()
s.bad()
s.fail()
s.good()
s.clear() // 恢复所有
s.clear(f)
s.setstate(f) // 置为某条件发生
s.rdstate() // 返回当前条件 返回类型strm::iostate
- testFile.cpp
作业
t8_3.cpp get.hpp
t8_6.cpp get.hpp
cout << “abc” << flush;
cout << “abc” << ends;
cout << “abc” << endl;
调bug时,根据最后一条输出确定崩溃的位置。多使用endl而不是’\n’,尽可能地让它在崩溃前将该刷新的输出出来
- 交互式程序cin与cout绑定:任何cin读操作都会首先刷新cout关联的的缓冲区
tie函数,一个流调用tie将自己绑定在传过来的实参流上,则它的任何IO操作都会刷新实参所关联的缓冲区
cin.tie(&cout);
ostream *old_tie = cin.tie();
cin.tie(0); // 释放tie
cin.tie(&cerr);
cin.tie(0);
cin.tie(old_tie); // 重新与cout绑定
- 文件流
ifstream infile(filename.c_str());
或
ifstream infile;
infile.open(filename.c_str());
if (!infile)
infile.close(); // 打开另一个前要关闭
infile.open(filename2.c_str());
如果要重用文件流读写多个文件,则在读另一个文件前要
infile.close();
infile.clear();
如果infile定义在while之中,那么每次都是干净的状态,不用clear都可以
- vector中存放在着多个文件的名字
读出来
t8_8.cpp
if (!input) {
cerr<< "error: can not open file: " << *it << endl;
input.clear();
++it;
continue;
}
//在vs提示符下d:/dev_projects/c++primer>c++primer < data/8_8
- t8_9.cpp读取文件内容,每个作为一个元素放到vector中
ifstreaminfile(fileName.c_str());
if(!infile) {//打开文件失败
return1;
}
stringstr;
while(getline(infile, str)) { // 行作为元素
//while(infile >> str) { // 单词作为元素 8-10
svec.push_back(str);
}
- 文件模式
in // ifstream和fstream , ifstream默认以in打开
out // ofstream fstream 会清空数据相当于也打开了trunc
ofstream默认以out打开
app // ofstream 追加
ate 打开后定位在尾
trunc // ofstream 清空数据
binary // 打开后以字节的形式重,不解释字符
ofstream outfile(“data/testfile”, ofstream::app);
fstream inout;
inout.open(“data/filename”, fstream::in |fstream::out);
ifstream& open_file(ifstream &infile,const string &filename) {
infile.close();
infile.clear();
infile.open(filename.c_str());
//if(!infile) // 不作判断
return in;
}
调用open_file()
ifstream infile;
if(! open_file(infile, fileName)) {
cout<< "error: can not open file:" << fileName << endl;
system("pause");
return-1;
}
get(infile);// 读取输出
get的实现
while (in >>ival, !in.eof()) {
if (in.bad()) {
throw runtime_error("IO streamcorrupted");
}
if (in.fail()) {
cerr << "bad data, tryagain!";
in.clear();
continue;
}
cout << ival << "";
}
- 字符串流
while (getline(cin, line)) {
istringstreamistrm(line);
while(istrm >> word) { // 处理这行中的每个单词
.....
}
}
stringstream strm;
stringstream strm(s);
strm.str(); // 返回那个string对象
strm.str(s);
stringstream可以提供转换和格式化
可使自动格式化
teststringstream.cpp
int val1 = 21, val2 = 23;
ostringstream out ;
out << "val1:" << val1<< "\nval2:" << val2 << "\n";
cout << out.str() << endl;
cout <<"=======================\n" << endl;
istringstream in(out.str()); // 关联到这行字符val1:21\nval2:23
string dump;
int a,b;
in >> dump >> val1 >> dump>> val2; // 换成a和b就不行
//忽略所取数据周围的符号,用dump
cout << val1 << " "<< val2 << endl;
cin.clear(istream::failbit); 在我机器上不管用
要使用cin.clear()
- 文件读入,每行作为一个元素放到vector中,再istringstream对象初始化为每个元素,处理每个单词,输出; 这都是很简单的程序
istringstream istr;
string word;
for (vector::const_iterator it= svec.begin();
it != svec.end(); ++it) {
istr.str(*it);
while (istr >> word) {
cout << word << “ ” ;
}
istr.clear(); // 定义在外面,所以将流设置为有效状态
}
第九章 顺序容器
- vector.swap(): 两个人换家,家里东西不搬动
vectorsv1(10);
vectorsv2(32);
sv1.swap(sv2);// 不会使迭代器失效, 还指向原来的元素,常数时间内交换,没有移动元素
cout<< sv1.size() << endl; // 32
cout<< sv2.size() << endl; // 10
- 9-28 不同类型容器间的赋值
char *sa[] = {“hello”,”good”, “nihao”, “leimng”};
list<char*> slst(sa, sa + 4);
vector<string>svec;
svec.assign(slst.begin(), slst.end());
- size是已经有的,capacity是再分配之前的容量,通常大于size, reserve(n), 其中n>capacity,则改变的是capacity=n不会改变size
while (ivec.size() != ivec.capacity()) ivec.push_back(0); // 只要还有剩余的容量就不会重新分配
ivec.push_back(0); 此时再查看一下size和capacity,知道库的分配策略了。
- 删除大写字母
for (string::iterator it = str.begin(); it !=str.end(); ++it) {
if(isupper(*it)) {
str.erase(it); // erase操作使it后移到被删元素的下一位置,而for中有++it,所以,下一步还原回来
--it; // 使迭代器指向被删除元素的前一位置
}
}
用while写可提高效率:
string::iterator it = str.begin();
while (it != str.end()) {
if(isupper(*it)) {
str.erase(it);
} else {
++it;
}
}
- const vector ivec(12);
vector::iterator it = ivec.begin();// error,const类型迭代器不能赋给非const类型的it
改正:vector::const_iteratorit = ivec.begin();
const vector<int>::iterator it =&ivec[0]; //error迭代器虽是指针,但不能这样赋值
- 任何无效迭代器的使用都会导致运行时错误 ,无法检查出来
向vector添加元素,如果空间不够,则重新加载,原迭代器全部失效。
容器可以调整大小resize(n),有可能使迭代器失效。 对于vector,deque,有可能全部失效; 对所有的容器,如果压缩,则可能使被删除元素的失效
- 元素类型size_type value_type, reference等同于value_type& 引用 ,写泛型程序非常用有
const_reference: value_type&
例子:vector::size_type size = ivec.size(); / ivec.max_size();
ivec.resize(100,3); // 调整大小为100,新加元素为3
- 向容器添加元素都是添加原值的副本
- list容器前插法
while (cin >> word)
iter= ilst.insert(iter.begin(), word);
等同于:ilst.push_front(word);
- 两个容器可以大小比较,其实是其元素在作比较,如果元素不支持,则容器也不支持
ivec1 < ivec2
要求完全匹配,即容器类型与元素类型都相同,且元素类型支持<操作
- 访问元素
list::referenceval = *ilst.begin(); // begin操作返回迭代器,其实是指针
list::reference val2 =ilst.front(); // front和back操作返回元素的引用
ivec::reference val3 = ivec[3]; 或 ivec.at(3); 随机访问类型的容器
- 删除元素:
c.erase(p);
c.erase(b, e); // 返回下一元素的迭代器
pop_back(); 删除最后一个元素并返回void
pop_front 不返回被删除元素的值,想拿到这个值,就要在删之前调用front或back函数
清除clear
- list是双向链表实现的 deque: 可以随机访问,中间插入元素代价高,但是在首尾删增元素代价小,且在首部增删元素不会使迭代器失效(当然删掉的就失效了)
如果读入的时候要在中间插,则可以先读入到list中,再拷到vector中,排一下序(书上说在拷之前排序)
- string与vector不同的是,不支持栈方式操作容器,没有front, back, pop_back
- vector cvec初始化string str(cvec.begin(), cvec.end())
对于一个string对象,要一次输入一百个字符,则先str.reserve(101);改一下capacity
- 以下是string的一些操作,构造,assign, erase, insert, append, replace, substr, find等等,使用的时候查
string构造另外方式:
string(c_array, n); //c_array可以是char *和char []的C风格字符串
string(s, pos2); //string类型s的下标pos2开始
string(s, pos2, len2);
替换str.assign(b, e); str.assign(n, t); //使用副本替换 返回string, 对于vector返回void
str.assign(s);// 以s的副本替换str
str.assign(c_array);
str.assign(s, pos2, len)
str.assign(cp, len)
str.erase(pos,len); // pos就是下标
str.insert(pos,cp); // 插入以NULL结尾的C风格串
str.insert(pos, s); // 插入s
str.insert(pos, s, pos2, len);
str.insert(pos, cp, len);
子串
str.substr(pos, n);
str.substr(pos);
str.substr(); // 返回str的副本
一系列的append和replace
查找:各种find, 返回类型string::size_type
find() // 找第一次出现
rfind() // 反向查找
find_first_of() // 任意字符的第一次出现 “0123456789” , 可以用来查找串中所有数字
find_last_of
find_first_not_of
find_last_not_of
没有找到if (find() != string::npos)
- 容器适配器
include
include
stack与queue默认是建立在deque之上,这种情况可以改变,通过第二类型实参来指定:
stack
第十章 关联容器
1.pair在#include
pair<int, string> p1 = make_pair(2, “good”);
pair<int, string> p2(3, “hello”);
p1 < p2; 先比第一个,若相等,则比较第二个
p1.first 这两个成员都是公有成员
p1.second
利用typedef pair<int, string> auther;
auther p3(4, “nihao”);
由于是公胡成员,所以可以cin >> p1.first >> p1.second;
- map类型
map的元素类型map
第十一章 算法
- size_t
back_inserter
- 提到的算法
find(vec.begin(), vec.end(), ival);
find_first_of(vec1.begin(), vec1.end(),vec2.begin(),vec2.end());
accumulate(vec.begin(), vec.end(), ival); // 第三个实参提供累加初始值和关联的类型
//写入运算不检查目标的大小是否满足写入的数目
fill(vec.begin(), vec.end(), 0); // 是写入安全的
fill_n(vec.begin(), 2, 0); // 前两个元素赋为0 ,必须保证vec至少有2个元素,否则运行错
fill_n(back_inserter(vec),2, 0); // 相当于在vec后面push_back两个0
back_inserter是迭代器适配器
copy(ilst.begin(), ilst.end(),back_inserter(ivec));
replace(ilst.begin(), ilst.end(), 0, 42); // 等于0的元素换成42
replace_copy(ilst.begin(), ilst.end(),back_inserter(vec), 0, 42); // ilst不变,复制到vec中,其中0换成42
sort(vec.begin(), vec.end());
vector<string>::iterator end_unique = unique(words.begin(),word.end()); // 把重复单词移动到后面去,返回无复下一位置
算法不改变容器大小,要删掉重复元素必须使用容器操作 words.erase(end_unique,words.end());
count_if(words.begin(), words.end(), GT6); //函数要求谓词函数只能一个参数,即迭代器范围内的元素
stable_sort(words.begin(), words.end(),isShorter);
以上四五行参考代码test统计六个及以上字母组成的单词.cpp
- front_inserter使用push_front,这个算法不能用在Vector上,它没有这个操作
inserter(ilst, it); //指定插入位置, 在it之前插
注意:copy(ilst.begin(), ilst.end(), inserter(ilst2,ilst2.begin()));//正序插入
//每次都在固定位置插入
copy(ilst.begin(),ilst.end(), front_inserter(ilst1));//逆序插入
list<int> ilst;
for (inti=0; i<5; ++i) {
ilst.push_back(i);
ilst.push_back(i);
}
ilst.sort();
printList(ilst);
ilst.unique(); // 删掉,而不是后移重复元素,这不是算法函数,所以真正修改了基础容器
// 真正修改基础容器的还有remove,算法函数中remove是把等于指定值的元素们
// 前移,返回第一个不等于val的元素的位置
printList(ilst);
类
1. 非static成员函数有this. Const必须同时出现在定义和声明中,不然出错
- 只有类中有很少的方法且有公有数据时,使用Struct,或只有公有数据时。
- 封装好处:一,防止用户级破坏,二,只要接口不变,不须改动用户级代码。 Cpp文件才是类定义文件
- 前向声明是不完全类型,只能用于定义指针或引用(只是定义,不能使用它们来访问成员),或声明使用该类型作为形参或返回值的函数。 可以定义该类型的static对象
- this的类型是一个指向类类型的const指针。
const成员函数只能返回*this作为一个const引用:对于返回*this或this的函数,const若有则必同时有
const Dog* run1() const { return this; } // const成员函数中,this是指向const对象的const指针,函数返回类型必须是const的
const Dog& run2() const { return *this; } // const成员函数中,*this是const对象,返回值只能是一个const的引用
- 基于成员函数是否const可以重载,基于形参是否指向const对象可以重载。Const对象使用const成员。
- 可变数据成员mutable,这样const成员函数可以改变它或它是const对象的成员时也可改
- 形参表和函数体处于类作用域中
Char Screen::get(indexr, index c) const { // index前不用加Screen
Indexrow = r*width;
return...
}
- Student *sp = new Student();
Student s = Student;
构造函数不能是const的。 创建const对象时,运行一个普通构造函数来初始化const对象
构造函数的初始化在冒号之后,在执行构造函数之前。初始化列表中没有写出来的成员的初始化情况:对于类类型则调用默认构造函数,对于内置或复合类型的初始化:(如果对象定义在局部作用域中,则不进行初始化,如果定义在全局作用域中,初始化为0),所以,含有内置类型或复合类型的类应该定义自己的构造函数来初始化这些成员,不应该依赖合成的构函。初始化顺序是成员的定义顺序,而非初始化列表写的顺序
必须在初始化列表中初始化:无默认构函的对象成员,const或引用成员
如果没有自己定义了构函,且都是单参并有各自的默认实参:error
explicit:避免了单参构函的形参类型到该类类型的隐式转换 它只能使用在类定义体内的构函前面的声明上,类定义体外此构函定义时前面不能加explicit
Studentstu = stu1; 执行复制构函
StudentStu(“leiming”, 24); // 直接初始化
Studentstu(stu1); // 复制构函
- 对于没有定义构函的且成员全是public的类可以使用Data val2 = {23, “abc”}的方式初始化
struct Data {int ival; char *p;};
- 友元可以是非成员函数,可以是已知类的成员函数,也可以是类
- 若要将成员函数定义为inline,则声明的时候可以不必写inline,它是用于定义的关键字。定义为inline