一. 第一天
1.基本算数类型
bool
char
wchar_t:用于扩展字符集
short int long
float double long double
2.字面值常量
以0开头的是八进制 以ox开头的是16进制
128u (unsigned)
1l (long)
3. 字符
‘A’ 表示单个字符A
“A” = “A \0” 表示 字母A和空字符 两个字符 字符串
4. 初始化
直接初始化和复制初始化
直接初始化更灵活更高效。
int ival(1024); //直接初始化
int ival = 1024; //复制初始化
初始化不是赋值
初始化指的是:创建变量并给它赋初值。
而赋值则是擦除对象的当前值并用新值代替。
5. 声明和定义
可以通过使用extern 关键字来声明变量名而不是定义它,也不分配存储空间。
程序变量可以声明多次,但只能定义一次。
如果声明有初始化式,即使有extern ,依然可以定义。
extern double pi = 3.14;
typedef:
- 为了隐藏特定类型的实现,强调使用类型的目的。
- 简化复杂类型定义。
- 允许一个类型用于多个目的,每次使用该类型的目的明确。
枚举
默认是 0.1.2.3…
如果第一个赋值是1,那么就从1开始
enum points{a,b,c,d};
6.类定义
用class和struct关键字定义类的唯一差别在于默认访问级别
默认情况下struct的成员为public
class的成员为private。
#define 指示接受一个名字并定义该名字为预处理器变量
#ifndef 指示检测指定的预处理器变量是否未定义。
如果预处理器未定义,那么跟在其后面的所有指示都被处理,知道出现#endif.
如果有一个被定义了,那么后面的就都被忽略了。
用来预防多次包含同一个文件。
#ifndef
#endif
7.string
getline() 读取一行文本,不包括换行符。
eg: getline(cin,line);
s.size();
string 比大小
string substr = "Hello";
string phrase = "Hello World";
string slang = "Hiya";
substr substr slang > phrase
string 获取字符
string 对象的下标从0开始,如果s是一个string对象且s不空,则s[0]就是字符串的第一个字符,…s[s.size()-1]则表示s的最后一个字符。
//判断字符串是否为空白字符,字母或数字。
//如果符合,返回一个int真值,否则返回0或负值
#include<cctype>
8.vector (容器)
末尾添加
v.push_back(t);
vector可以和string一样,进行下标操作。
但下标操作不添加元素。
仅能对确知已存在的元素进行下标操作。
9.迭代器
每个容器都定义了一个名为iterator的类型,而这种类型支持迭代器的各种操作。
vector<int>:: iterator iter;
begin():返回迭代器指向第一个元素。
end():返回迭代器指向vector的“末端元素的下一个”,超出末端迭代器,表明它指向一个不存在的元素。只是起哨兵的作用,表示我们已经处理完了vector中的元素。
如果vector为空,那么begin()和end()返回的元素相同。
迭代器类型可以使用 解引用操作符(*) 来访问迭代器所指向的元素。end()操作不可以。
*iter = 0;
用==或!=来比较两个迭代器所指的元素。
每种容器类型还定义了一个const_iterator的类型。只能用来读取容器内的元素,但不能改变其值。
1 迭代器和整形值加减:生成一个新的迭代器,位置在iter所指的元素之前或之后n个位置。
iter+n;
iter-n;
2 迭代器和迭代器加减:用来计算两个迭代器对象的距离,
iter1-iter2;
两者必须指向同一vector中的元素。
任何改变vector长度的操作都会使已存在的迭代器失效。
10 bitset
bitset类对象的区别仅在其长度而不在其类型。在定义时,要明确bitset含有多少位。
按位置来访问,位集合的位置编号是从0开始的。
用string初始化bitset,方向是从右往左的。反向转化。
string strval("1100");
bitset<32> bitvec4(strval);
bitvec4:00110000...
二. 第二天
1. 指针
避免使用未初始化的指针建议程序员在使用之前初始化所有变量,尤其是指针。
把int类型赋值给指针是非法的。允许把数值0或在编译时可获得0值的const量赋给指针。
const int val = 0;
pi = cal;
pi = 0;
或者使用在< cstdlib >头文件中定义的NULL。
void*,可以保存任何类型对象的地址。
不过只支持几种操作:
- 与另一个指针比较
- 向函数传递void指针或从函数返回void指针
- 给另一个void*指针赋值
指针提供 间接操纵 其所指对象的功能。
string s("Hello");
string *sp = &s;
cout<<*sp; //prints hello
通过指针进行赋值
//可以修改指针所指对象的值
*sp = "good";
给指针赋值
//也可以修改指针sp本身的值,使用sp指向另外一个新对象。
string s2 = "some";
sp = &s2;
指针和引用的比较 : 都可间接访问另一个值。
- 引用总是指向某个 对象,定义引用时没有初始化式错误的。
- 赋值行为的差异。给引用赋值修改的是该引用所关联对象的值,而并不是使引用与另一个对象关联。引用一经初始化,就是种指向同一个特定的对象。
int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
cout << *pi << ' ' << pi << endl;
cout << *pi2 << ' ' << pi2 << endl;
pi = pi2;
cout << *pi << ' ' << pi << endl;
cout << *pi2 << ' ' << pi2 << endl;
int &r1 = ival, &r2 = ival2;
cout << r1 << endl;
cout << r2 << endl;
r1 = r2;
cout << r1 << endl;
cout << r2 << endl;
1024 0x61fdfc
2048 0x61fdf8
2048 0x61fdf8
2048 0x61fdf8
1024
2048
2048
2048
指向指针的指针
int ival = 1024;
int *pi = &ival;
int **ppi = π
int *pi2 = *ppi;
cout << ival << ' ' << &ival << endl;
cout << *pi << ' ' << pi << endl;
cout << **ppi << ' ' << *ppi << ' ' << ppi << endl;
cout << *pi2 << ' ' << pi2 << endl;
1024 0x61fe0c
1024 0x61fe0c
1024 0x61fe0c 0x61fe00
1024 0x61fe0c
只要指针指向数组元素,就可以对他(指针)进行下标操作
C++允许计算数组或对象的超出末端的地址,但不允许对此辞职进行解引用操作。
计算数组超过末端位置之后的数组首地址之前的地址都是不合法的。
C++允许使用指针遍历数组。
强制要求指向const对象的指针也必须具有const特性。
但是允许const指针指向非const对象。
const double *cptr;
const限定了cptr指向的对象类型,不是指针本身。
在定义时不需要进行初始化,如果需要的话,允许给指针重新赋值,使其指向另一个const对象。
但不能通过const指针修改所指对象的值。
可以修改const指针所指向的值
由于没法分辨const指针所指的对象是否为const,那么就把const指针所指向的对象化都视为const。
//给我绕晕了。。。
不能保证指向const的指针所指的对象的值一定不可修改。
const指针----本身的值不能修改。
int errNumb = 0;
int *const curErr = &errNumb;
currErr是指向int型对象的const指针。指针的值不能修改,不能指向其他对象。
const指针必须在定义时初始化
指针所指对象的值能否修改完全取决于该对象的类型
指向const对象的const指针
const double pi = 3.14;
const double *const po_ptr = π
pi_ptr首先是一个const指针,指向double类型的const对象。
2.C风格字符串
使用标准库函数strncat和strncpy比strcat和strcpy函数更安全。
在赋值和串联字符串时,一定要时刻记住算上结束符null。
3.创建动态数组
如果在自由存储区(堆)中创建的数组存储了内置类型的const对象,则必须为这个数组提供初始化。
用new动态创建长度为0的数组是合法的。
使用数组初始化vector
const size_t s = 6;
int int_arr[s] = {0,1,2,3,4,5};
vector<int> ivec (int_arr,int_arr+s);
三. 第三天
1.操作符
一元操作符优先级最高。
如果操作数只有一个是负数,那么除法和求模操作的结果取决于机器。
21 % -5 = 1 or -4
21 / -5 = -4 or -5
短路求值:逻辑与和逻辑或操作符总是先计算左操作数,仅靠左操作数的值无法确定该逻辑表达式的结果时,才会求解其右操作数。
对于位操作符,由于系统不能确保如何处理其操作数的符号位,所以强烈建议使用unsigned整形操作数。
2. bitset
bitset<30> bitset_quizl;
//1
bitset_quizl.set(27);
//先右移 再判断
int_quizl |= 1UL<<27;
//0 复位
bitset_quizl.reset(27);
int_quizl &=~(1UL<<27);
3.操作符
移位操作符具有中等优先级:其优先级比算数操作符低,但比关系操作符、赋值操作符和条件操作符优先级高。(cout<< 算数操作符可以不加小括号)
赋值操作具有右结合性。同种类型的值可以多次赋值。
前自增操作:只需要加1后返回加1后的结果即可。
后自增操作:必须先保存操作数原来的值,以便返回未加1之前的值作为操作的结果。
vector<int>::interator iter = ivec.begin();
while(...)
cout<<*iter++<<endl;
输出元素后进行自增。
点操作符用于获取类型对象的成员。
如果有一个指向Sales对象的指针(或迭代器),则在使用点操作符之前,需要对**该指针(或迭代器)**进行解引用以用的指定的Sales对象。
解引用优先级低于点操作符。
Sales *sp = &item1;
(*sp).same(item2);
使用箭头操作符替换 指针(或迭代器)对象
sp->same(item2);
4.内存分配和删除
sizeof:
- 对引用类型做sizeof操作将返回存放此引用类型对象所需的内在空间大小。
- 对指针做sizeof操作将返回存放指针所需的内在大小。如果要获取该指针所指向对象的大小,则必须对指针进行引用。
- 对数组sizeof= 对元素类型sizeof * 数组元素个数。
C++保证,删除0值的指针是安全的。
在删除指针后,该指针变成悬垂指针。悬垂指针指向曾经存放对象的内存,但该对象已经不存在了。悬垂指针往往导致程序错误,而且很难检测出来。
一旦删除了指针所指的对象,立即将指针设置为0,清楚表明指针不再指向任何对象。
const对象
//new返回指针
//const对象创建的时候必须进行初始化。
const int *pci = new const int(1204);
5.类型转换
隐形转换:
- 在混合类型表达式中,操作数被转换为相同的类型。 >=
- 用作条件的表达式被转换为bool类型。
- 用表达式初始化某个变量,或将一表达式赋值给某个变量,被转换为该变量的类型。
- 在函数调用中。
- 。。。。。
- 在使用数组时,大多数情况下数组会自动转换成指向第一个元素的指针。
- 指向任意数据类型的指针都可以转换成void*类型。
- 整型数值常量0可以转换为任意指针类型。
- 。。。。。
- 使用非const对象初始化const对象的引用时,系统将非const对象转换为const对象。
- 将非const对象的地址转换为指向相关const类型的指针。
显示转换(强制类型转换cast):
double dval;
int ival;
ival *=dval;
static_cast dynamic_cast const_cast reinterpret_cast
//ival int->double ->int >赋值
//去掉int -> double 的操作
ival *= static_cast<int>(dval);
命名的强制类型转换符号。
cast-name<type>(expression)
- static_cast 编译器隐式执行的任何类型转换都可以显示完成。
- dynamic_cast 支持运行时识别指针或引用所指向的对象。
- const_cast 转换低调表达式的const性质。
- reinterpret_cast 通常为操作数的位模式提供较低的重新解释。reinterpret_cast 本质上依赖于机器,要求程序员完全理解所涉及的数据类型以及编译器实现强制类型转换的细节。
int *ip;
char *pc = reinterpret_cast<char*>(ip);
char *pc = (char*) ip;
程序员必须永远记得pc所指向的真是对象其实是int型。
避免使用强制类型转换
6. 异常
7. 预处理
可使用NDEBUG预处理变量实现有条件的调试代码。
int main()
{
#ifndef NDEBUG
cerr<<"starting main" <<endl;
#endif
}
如果NDEBUG未定义,那么程序就会将信息写到cerr中,如果已经定义了,那么程序执行时会跳过#ifndef和#endif之间的代码。
另一个常见的调试技术是使用NDEBUG预处理变量和assert预处理宏。
assert宏是在cassert头文件中定义的。
assert(Expr);
只要NDEBUG未定义,assert宏就求解条件表达式expr,如果结果位false,assert输出信息并且终止程序进行。如果表达式有一个非0,那么assert不做任何操作。
与异常不同(异常用于处理程序执行时预期要法神更多错误),程序员使用assert来测试“不可能发生”的条件。不能用来代替运行时的逻辑检查,也不能代替对程序可能产生的错误的检测。
assert(word.size()>threshold);
四.第四天
1.函数调用
函数调用:
- 对应的实参初始化函数的形参
- 将控制权转移给被调用函数。朱调函数的执行被挂起,被调函数开始执行。
C++是一种静态强类型语句,对于每一次的函数调用,编译时都会检查其形参。
调用函数时,对于每一个实参,其类型都必须与对应的形参类型相同,或具有可被转换为该形参类型的类型。
函数的形参表为编译器提供了检查实参需要的类型信息。
形参的初始化与变量的初始化一样:如果形参具有非引用类型,则复制实参的值,如果形参为引用类型,则它只是实参的别名。(如果是对象的话,不是引用类型就会调用复制构造函数。)
圆回来了。。。
2.非引用实参
普通的***非引用类型的参数通过复制对应的实参实现初始化***。当用实参副本初始化形参时,函数并没有访问调用所传递的实参本身,因此不会修改实参的值。
3.指针、引用形参
此时将**复制实参指针 ,被复制的指针只影响对指针的赋值。如果函数形参时非const类型的指针,则函数可通过指针实现赋值,修改指针所指对象的值。如果保护指针指向的值,则形参需定义指针为const。
void reset(int *ip)
{
*ip = 0; //change
ip = 0;//unchange
}
int i = 42;
int *p = &i;
reset(p);
cout<<*p<<endl; // 输出0
尽管函数的形参是const,但是编译器将其声明为普通的,为了支持对C语言的兼容。
不适合赋值实参:
- 需要在函数中修改实参的值。
- 以大型对象作为实参传递。
- 没有办法实现对象的赋值。
- 。。。。。。。。。。。。
- 将形参定义为引用或指针类型。使用引用形参更安全和更自然。
4.使用引用形参返回额外的信息
在一个整形vector对象的元素中搜索某个特定值。如果找到满足要求的元素,则返回指向该元素的迭代器。否则返回一个迭代器,指向该vector对象的end操作返回的元素。如果该值初夏你了不止一次,还希望函数可以返回其出现的次数,返回的爹地啊器应该指向具有要寻找的值的第一个元素。
#include <vector>
using namespace std;
vector<int>::const_iterator find_val(
vector<int>::const_iterator beg,
vector<int>::const_iterator end,
int value,
vector<int>::size_type &occurs)
{
vector<int>::const_iterator res_iter = end;
occurs = 0;
for (; beg != end; ++beg)
{
if (*beg == value)
{
res_iter = beg;
}
++occurs;
}
return res_iter;
}
形参时引用,传递一个右值或具有需要转换的类型的对象是不允许的。
5.传递指向指针的引用
//指针交换
void ptrswap(int *&v1,int *&v2)
{
int *tmp = v2;
v2 = v1;
v1 = tmp;
}
v1是一个引用,与指向int型对象的指针向关联。v1只是传递进ptrswap函数的任意指针的别名。
vector和其他容器类型的形参
通常,函数不应该有vector或其他标准库容器类型的形参。调用含有普通的非引用vector形参的函数将会复制vector的每一个元素。考虑加你个形参声明为引用类型,但是C++程序员倾向于通过出传递指向容器中需要处理的元素的迭代器来传递容器。
6.数组形参
- 不能复制数组。—无法编写使用数组类型形参的函数
- 使用数组名字是,数组名会自动初始化为指向其第一个元素的指针。–处理数组的函数通常通过操纵指向数组指向其中元素的指针来处理数组。
//等价
void printV(int*); //更好,不容易引起误解。
void printV(int[]);
void printV(int[]10);
当编译器检查数组形参关联的实参时,只会检查实参是不是指针、指针的类型和数组元素的类型时是否匹配,而不会检查数组的长度。
通过引用传递数组
编译器不会将数组实参转化为指针,而是传递数组的引用本身。数组大小称为形参和实参类型的一部分,编译器检查数组的实参的大小与形参的大小是否匹配。
void printV(int (&arr)[10]);
二维数组
void printV(int (matrix*)[10],int rowSize);
三种常见的编程技巧确保函数的操作不超过数组实参的边界。
- 在数组本身防止一个标记来检测数组的结束。
- 传递指向数组第一个和 最后一个元素的下一个位置 的指针。
void printV(const int *beg,const int *end)
{
while(beg!=end)
{
cout<<*veg++<<endl;
}
}
int main()
{
int j[2] = {0,1};
printV(j,j+2);
return 0;
}
- 将第二个形参定义为表示数组的大小。
void printV(const int ia[],size_t size)
{
for(size_t i = 0;i!=size;++i)
{
cout<<ia[i]<<endl;
}
}
int main()
{
int j[] = {0,1};
printV(j,sizeof(j)/sizeof(*j));
return 0;
}
7.含有可变形参的函数
对于C++程序,只能将简单数据类型传递给含有省略符形参的函数。实际上,当需要传递给省略符形参时,大多数类类型对象都不能正确的复制。
vpid foo(parm_list,...);
void foo(...);
8.返回值
void 函数可以return 另一个void函数。
void函数中,return; 相当于提前结束。
非引用返回值:在调用函数的地方会将函数返回值复制给临时对象。其返回值可以是局部对象,也可以是求解表达式的结果。
返回引用:没有复制返回值,返回的是对象本身。
千万不能返回局部变量的引用!
千万不能返回局部对象的指针!
返回引用左值:
char &get_val(string &str,string::size_type ix)
{
return str[ix];
}
int main()
{
string s("a value");
cout<<s<<endl;
get_val(s,0)='A';
cout<<s<<endl; //print A
return 0
}
如果不希望引用返回值被修改,返回值迎声明为const;
const char &get_val();
设计带有默认实参的函数,使最少使用默认实参的形参排在最前,最可能使用默认实参的形参排在最后。
9.静态局部对象
一个变量如果位于函数的作用域内,但声明期跨越了这个函数的多次调用,定义为static。
static局部对象确保不迟于在程序执行流程第一次经过该对象的定义语句时进行初始化。一旦被创建,在程序结束之前不会被撤销。静态局部对象会持续存在并保持它的值。
10.内联函数
const string &shorterString(const string &s1,const string &s2)
{
return s1.size() < s2.size() ? s1: s2;
}
好处·:
- 阅读和理解函数更容易。
- 修改函数更容易。
- 使用函数可以确保统一的行为,每个测试都保证以相同的方式实现。
- 函数可以重用。
但是:调用函数比求解等价表达式要慢得多。
调用函数:调用前要先保存寄存器,并再返回时恢复;复制实参;程序还必须转向一个新位置执行。
将函数指定为inline函数。将在编译时展开。
消除了写成函数的额外执行开销。
inline说明对于编译器来说只是一个建议,编译器可以选择忽略。
11.类的成员函数
#include <iostream>
using namespace std;
class Sales_item
{
public:
double avg_price() const;
bool same_isbn(const Sales_item &rhs) const
{
return isbn == rhs.isbn;//return this->isbn == rhs.isbn);
}
//构造函数
Sales_item():units_sold(0),revenue(0.0){};
private:
std::string isbn;
unsigned units_sold;
double revenue;
};
double Sales item::avg price() const
{
if(units_sold) return revenue/units_sold;
else return 0;
}
编译器隐式将在类内定义的成员函数当做内联函数。
total.same_isbn(trans);
this 是指向total对象的指针。
11.重载确定的三个步骤
- 候选函数
候选函数时与被调函数同名的函数,并且在调用点上,它的声明可见。 - 选择可行函数
- 函数的形参个数与该调用的实参个数相同。
- 每一个实参的类型必须与对应的形参的类型匹配,或者可被隐式转换为对应的形参类型。
- 寻找最佳匹配(如果有的话)
考虑函数调用的每一个实参,选择对应形参与之最匹配的一个或多个可行函数。原则是实参类型与形参类型越接近则匹配越好。 - 含有多个形参的重载确定
- 每个实参的匹配都不劣于其他可行函数需要的匹配。
- 至少有一个实参的匹配优于其他可行函数提供的匹配。
- 否则调用错误,编译器提示该调用具有二义性。
仅当形参时引用或指针时,形参是否为const才有影响。
f(int *);
f(int *const);
const都用于修改指针本身,而不是修饰指针所指向的类型。都赋值了指针,指针本身是否为const并没有带来区别。当形参以副本传递时,不能基于形参是否为const来实现重载。
12.指向函数的指针
bool (*pf)(const string &,const string &);
将pf声明为指向函数的指针,它指向的函数带有两个string&类型的形参和bool类型的返回值。
typedef bool (*cmpFcn)(const string &,const string &);
函数指针只能通过同类型的函数或函数指针或0值常量表达式进行初始化或赋值。
cmpFcn pf = lengthCompare;
lengthCompare("hi","bye");
pf("hi","bye");
(*pf)("hi","bye");
如果指向函数的指针没有初始化,或者具有0值,则该指针不能在函数调用中使用。
函数指针形参可以是指向函数的指针
声明方式:
void useBigger(const string &,const string &,bool(const string &,const string &));
void useBigger(const string &,const string &,bool(*)(const string &,const string &));
返回指向函数的指针
int (*ff(int))(int*,int);
ff(int);
将ff声明为一个函数,它带有一个int型的形参,函数返回
int(*)(int*,int);
是一个指向函数的指针,它所指向的函数返回int型并带有两个分别是int*和int的形参。
具有函数类型的形参所对应的实参将被自动转换为指向相应函数类型的指针。
指向重载函数的指针
extern void ff(vector<double>);
extern void ff(unsigned int);
void (*pf1)(unsigned int) = &ff; //unsigned
指针的类型必须与重载函数的一个版本精确匹配。