- 开始
从命令行运行编译器
$ CC prog1.cc
注释:以 /* 和 */ 开始结束的注释不能嵌套,尽量使用单行注释,注释快捷键ctrl+shift+/
- 变量和基本类型
bool | |
---|---|
char | 8位 |
wchar_t | 16位 |
char16_t | 16位 |
char32_t | 32位 |
short | 16位 |
int | 16位 |
long | 32位 |
long long | 64位 |
float | 6位有效数字 |
double | 10位有效数字 |
long double | 10位有效数字 |
当明确知晓数值不可能为负时,选用无符号类型;
执行浮点数运算选用double;
负数转换为无符号数时等于这个负数加上无符号数的模。
20 | 十进制 |
---|---|
024 | 八进制 |
0x14 | 十六进制 |
转义序列 | |
---|---|
换行符 | \n |
纵向制表符 | \v |
反斜线 | \ |
回车符 | \r |
横向制表符 | \t |
退格符 | \b |
问号 | ? |
进纸符 | \f |
报警符 | \a |
双引号 | \" |
单引号 | \’ |
- 初始化
int a = 0;
int a = {0};
int a{0};
int a(0);
- const限定符
const对象必须初始化
如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字
extern const int bufSize = fcn();
- const的引用
int i = 42;//允许将const int&绑定到普通int对象上,但反过来则不行
const int &r1 = i;
const int &r2 = 42;
const int &r3 = r1 * 2;
int &r4 = r1 * 2;//错误
- const的指针
int errNumb = 0;
int *const curErr = &errNumb;//curErr将一直指向errNumb,即该指针是常量,但指针指向的对象并不是常量,可以改变
const double pi =3.14159;
const double *const pip = π//pip是一个指向常量对象的常量指针
顶层const:指针本身是个常量
底层const:指针指向的对象是个常量
int i = 0;
int &const p1 = &i;//不能改变p1的值,这是一个顶层const
const int ci = 42;//不能改变ci的值,这是一个顶层const
const int *p2 = &ci;//允许改变p2的值,这是一个底层const
const int *const p3 = p2;//靠右的是顶层const,靠左的是底层const
const int &r = ci;//用于声明引用的const都是底层const
实际原则应该就是非常量可以等于常量,反之则不成
- constexpr
//constexpr指值不会改变且在编译过程就得到计算结果的表达式
constexpr int mf = 20;//必须用常量或常量表达式初始化
constexpr int limit = mf + 1;
constexpr int sz = size();//只有当size()是一个constexpr函数时才正确
与const有所不同,constexpr只对指针生效,与指针所指对象无关
- typedef和using
typedef double wages;//wages是double的同义词
typedef wages base, *p;//base是double的同义词,p是double*的同义词
using SI = Sales_item;//SI是Sales_item的同义词
//一个常量指针类型别名的易错例子
typedef char *pstring;
const pstring cstr = 0;//cstr是指向char的常量指针
const pstring *ps;//ps是一个指针,它的对象是指向char的常量指针
//const是对给定类型的修饰,pstring实际上是指向char的指针,因此,const pstring就是指向char的常量指针,而非指向常量字符的指针
const char *cstr = 0;//const pstring cstr的一种错误理解
//声明语句中用到pstring时,其基本数据类型是指针
//用char*重写该语句后,数据类型就变成了char,*成为了声明符的一部分
//这样改写的结果就是const char成了基本数据类型
//前者声明了一个指向char的常量指针,后者声明了一个指向const char的指针
- auto
auto必须有初始值,并且该语句中所有变量的基本数据类型必须一样
//auto一般会忽略掉顶层const
int i= 0,&r = i;
auto a = r;
const int ci = i,&cr = ci;
auto b = ci;//b是一个整型(ci的顶层const被忽略掉了)
auto c = cr;//c是一个整型(cr是ci的别名,ci本身是一个顶层const)
auto d = &i;//d是一个整型指针
auto e = &ci;//e是一个指向整型常量的指针
- decltype
选择并返回操作数的数据类型
const int ci = 0,&cj = ci;
decltype(ci) x = 0;//x的类型是const int
decltype(cj) y = x;//y的类型是const int&,y绑定到变量x
decltype(cj) z;//错误,引用必须初始化
//decltype的表达式如果是加上了括号的变量,结果将是引用
decltype((i)) d;//错误:d是int&,必须初始化
decltype(i) e;//正确:e是一个(未初始化的)int
//decltype后如果跟的是双括号,结果永远都是引用
- 字符串、向量和数组
- string
初始化
string s1;
string s2 = s1;
string s3 = "hiya";
string s4(10,'c');
string s5("value");
string s6(s1);
string操作 | |
---|---|
os<<s | 将s写到输出流当中,返回os |
is>>s | 从is中读取字符串赋给s,字符串以空白分割,返回is |
getline(is,s) | 从s中读取一行赋给is |
s.empty() | s为空返回true,否则返回false |
s.size() | 返回s中字符的个数 |
两个对象操作时若想操作结果还是string,则必须确保每个加法运算符(+)的两侧的运算对象至少有一个string;
※诸如“hello”这种不是string,是char
//小tip:一个处理string中每个字符的小代码段
string s("Hello World!");
//转换成大写形势
for(auto &c : s)//注意:c是引用
c = toupper(c);//c是一个引用,因此赋值语句将改变s中字符的值
cout<< s <<endl;
//只有用引用才能改变s中的值,否则改变的只是临时变量
- vector
初始化
vector<T> v1
vector<T> v2(v1)
vector<T> v2 = v1
vector<T> v3(n,val)
vector<T> v4(n)
vector<T> v5{a,b,c...}
vector<T> v5 = {a,b,c...}
※push_back
vector<int> v2;
for(int i = 0;i != 100;i++)
v2.push_back(i);//依次把整型值放在v2尾端
vector只能对确知已存在的元素执行下标操作
//错误示例
vector<int> ivec;
for(decltype(ivec.size()) ix = 0;ix != 10; ++ix)
ivec[ix] = ix;//错误:ivec不包含任何元素
迭代器
begin负责返回指向第一个元素的迭代器,end负责返回指向容器尾元素的下一位置的迭代器
迭代器运算符 | |
---|---|
*iter | 返回iter所指元素的引用 |
iter->mem | 解引用iter并获取该元素的名为mem的成员,等价于(*iter).mem |
++iter | 令iter指向容器中的下一个元素 |
–iter | 令iter指向容器中的上一个元素 |
iter1 == iter2 | 判断两个迭代器指示的元素是否相等 |
iter1 != iter2 |
迭代器类型
iterator:读写
const_iterator:只读
迭代器返回的类型由元素本身的类型决定,只有使用cbegin和cend时返回的才永远都是常量,即const_iterator
//解引用操作
(*it).empty();//正确,解引用it,调用对象的empty成员
*it.empty;//错误,试图访问it的名为empty的成员,但it是个迭代器,没有empty成员
箭头运算符:(->)
it->mem和(*it).mem表达的意思相同
目前已知的vector对象的操作对迭代器的限制:不能在范围for循环中向vector添加元素;任何一种可能改变vector对象容量的操作,比如push_back,都会使该vector对象的迭代器失效
迭代器二分搜索:
int main()
{
vector<string> text{ "1","2","3","4","5","6","7","8","9","0" };
auto beg = text.begin(), end = text.end();
auto mid = text.begin() + (end - beg) / 2;
while (mid != end && *mid != "1")
{
if ("1" < *mid)
end = mid;
else
beg = mid + 1;
mid = beg + (end - beg) / 2;
cout << *mid << endl;
}
return 0;
}
- 数组
初始化
//纬度必须是常量
unsigned cnt = 42;//不是常量表达式
constexpr unsigned sz = 42;//常量表达式
int arr[10];//含有10个整数的数组
int *parr[sz];//含有42个整型指针的数组
string bad[cnt];//错误:cnt不是常量表达式
string strs[get_size()];//只有当get_size是constexpr时正确
不存在引用的数组;
不允许拷贝和赋值。
指针和数组
string num[] = {"one","two","three"};
string *p = &nums[0];//指向nums的第一个元素
string *p2 = num;//和上面一行等价
int ia[] = {0,1,2,3,4,5,6,7,8,9};
auto ia2(ia);//ia2是一个整型指针,指向ia的第一个元素
auto ia2(&ia[0]);//和上面一行等价
//标准库函数begin和end
int *beg = begin(ia);//指向ia首元素的指针
int *last = end(ia);//指向ia尾元素的下一位置的指针
数组内置的下标运算符索引值不是无符号类型,这一点与vector和string不一样
c风格字符串
c风格字符串函数 | |
---|---|
strlen() | 返回长度,空字符不计算在内 |
strcmp(p1,p2) | 若p1大于p2,返回一个正值,相反同理 |
strcat(p1,p2) | 把p2附加到p1,返回p1 |
strcpy(p1,p2) | 把p2拷贝给p2,返回p1 |
传入此类函数的指针必须指向以空字符作为结束的数组
//以空字符结束的字符数组可以初始化string或为string赋值
char *str = s;//错误:不能用string对象初始化char
const char *str = s.ctr();//正确
//使用数组初始化vector对象
int int_arr[] = {0,1,2,3,4,5};
vector<int> ivec(begin(int_arr),end(int_arr));
多维数组
//使用引用对多维数组进行循环操作,避免数组被自动转成指针
//一个错误例子
for(auto row : ia)
for(auto col : row)
cout<< col <<endl;//无法通过编译,因为第一个row被转为指针int*,如此第二个循环就不合法了。
for(auto &row : ia)
for(auto &col : row)
cout<< col <<endl;//正确写法
//多维数组指针操作
int ia[3][4];
for(auto p = ia;p != ia + 3; ++p)
{
for(auto q = *p;q != *p + 4; ++q)
cout<< *q <<endl;
}
- 表达式
- 取余和除法运算
左右对应 | |
---|---|
(-m/n)和m/(-n) | -(m/n) |
m%(-n) | m%n |
(-m)%n | -(m%n) |
- 成员访问运算符
ptr->mem等价于*(ptr).mem
解引用运算符的优先级低于点运算符
*p.size();//是错误的,p是一个指针,它没有名为size的成员
(*p).size();//正确
- 条件运算符
cond?expr1:expr2 嵌套时从左至右判断 - sizeof运算符
在sizeof的运算对象中解引用一个无效指针仍然是一种安全的行为,因为指针实际上并没有被真正使用,sizeof不需要真的解引用也能知道它所指对象的类型 - ※类型转换
bool flag;
char cval;
short sval;
int ival;
long lval;
float fval;
unsigned short usval;
unsigned int uival;
unsigned long ulval;
double dval;
//一些例子
3.14159L+'a';//'a'提升为int,然后该int值转换为long double
dval+ival;//ival转换为double
dval+fval;//fval转换为double
ival=dval;//dval切除小数部分后转换为int
flag=dval;//如果dval是0,则flag是false,否则是true
cval+fval;//cval提升为int,然后该int转换为float
sval+cval;//sval和cval都提升为int
cval+lval;//cval转换为long
ival+ulval;//ival转换为unsigned long
usval+ival;//根据unsigned short和int所占空间的大小进行提升
uival+lval;//根据unsigned int和long所占空间的大小进行转换
显式转换
static_cast<new_type>expression
一种强制类型转换;
只要不包括底层const就都可以使用;
不能将const类型转换为非const类型;
可以将void指针转换为初始指针类型,也可以将初始指针类型转换为void类型的指针
cosnt_cast<new_type>expression
const_cast只能改变运算对象的底层const
const char *pc;
char *p = const_cast<char*>(pc);//正确:但是通过p写值是未定义的行为
只有const_cast能改变表达式的常量属性;
不能用const_cast改变表达式的类型;
- 语句
- switch
如果某个case标签匹配成功,将从该标签开始往后顺序执行所有case分支,除非程序显式地中断了这一过程,否则直到switch的结尾处才会停下来。要想避免执行后续case分支的代码,我们必须显式地告诉编译器终止执行过程。大多数情况下,在下一个case标签之前应该有一条break语句。
unsigned vowelcnt = 0;
//...
switch(ch)
{
//出现aeiou任意一个都会将vowelcnt加1
case 'a';
case 'e';
case 'i';
case 'o';
case 'u';
++vowelcnt;
break;
}
如果没有任何一个case标签能匹配上switch表达式的值,程序将执行紧跟在default标签后面的语句
break:可以存在于while、do while、for、switch
continue:可以存在于for、while、do while
- 异常处理
throw:throw用来(raise)引发异常
try:以一个或多个catch子句结束。try抛出的异常被catch子句处理
try{
program-statements
}catch(exception-declaration){
handler-statements
}catch(exception-declaration){
handler-statements
}//…
//异常处理
//若除数为0,抛出异常
double division(int a, int b)
{
if (b == 0)
{
throw "Division by zero condition!";
}
return (a / b);
}
int main()
{
int x = 50;
int y = 0;
double z = 0;
try {
z = division(x, y);
cout << z << endl;
}
catch (const char* msg) {
cerr << msg << endl;
}
return 0;
}
- 函数
- 局部对象
局部静态对象:在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响
size_t count_calls()
{
static size_t ctr = 0;//调用结束后这个值仍然有效
return ++ctr;
}
int main()
{
for(size_t i = 0;i != 10;++i)
cout<< count_calls() <<endl;
return 0;
}
不能返回局部对象的引用或指针
//试图返回局部对象的引用
const string &mainp()
{
string ret;
//以某种方式改变一下ret
if(!ret.empty())
return ret;//错误:返回局部对象的引用
else
return "Empty";//错误:"Empty"是一个局部临时量
}
- 传引用形参
通过使用引用形参,允许函数改变一个或多个实参的值
//返回s中c第一次出现的位置索引
//引用形参occurs负责统计c出现的次数
string::size_type find_char(const string &s,char c,string::size_type &occurs)
{
auto ret = s.size();
occurs = 0;
for(decltype(ret) i = 0;i != s.size();++i)
{
if(s[i] == c)
{
if(ret == s.size())
ret = i;
++occurs;
}
}
return 0;
}
- 重载函数
编译器根据传递的实参类型判断使用哪个函数;
不允许两个函数除了返回类型外其他要素都相同
※重载和const形参
Record lookup(Account&);
Record lookup(const Account&);
Record lookup(Account*);
Record lookup(const Account*);
//上面的重载函数都是正确的,编译器可以通过实参是否是常量来推断调用哪个函数
//因为const不能转换为非const,因此只能把const实参传给const形参
//但非const可以转换为const,因此非常量形参可以调用以上任意函数
- 特殊用途语言特性
一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值
- 函数指针
//函数指针指向的是函数而非对象
bool (*pf)(const string &,const string &);
//pf两端的括号必不可少,如果不写括号,则pf是一个返回值为bool指针的函数
返回指向函数的指针
using F = int(int*,int);//F是函数类型,不是指针
using PF = int(*)(int*,int);//PF是指针类型
PF f1(int);//正确:PF是指向函数的指针,f1返回指向函数的指针
F f1(int);//错误:F是函数类型,f1不能返回一个函数,返回了一个int
F *f1(int);//正确:显式地指定返回类型是指向函数的指针
int (*f1(int)(int*,int);//直接声明
auto f1(int) -> int(*)(int*,int);//尾置返回类型声明
至此,类前的复习完毕