文章目录
知识杂记
表达式
-
完整表达式是指该表达式不是另一个更大表达式的子表达式。
-
对于
y = (4 + x++) + (6 + x++);
c++并不能保证每个子表达式计算完后x的值+1,只能保证整个表达式计算完后x+2。所以要避免这种情况。 -
>,< 的结合方向从左往右。
前缀格式++a
与后缀格式a++
对于内置类型,两者执行速度一样。
对于类(用户设置的类型),前缀更快。
后缀比前缀优先级高,前缀优先级和解除引用运算符的优先级一样高。
(优先级一样高时从右往左)
运算符优先级
逗号运算符
优先级最低
从第一个表达式开始计算,再到第二个…
逗号表达式的值为最后一个表达式的值。
左值,右值
- C语言中,左值可以位于赋值句的左侧,右值不能
- 当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值时,用的是对象的身份(在内存中的位置)、
- 之前见过的一些左右值:
- 赋值运算符(=) 的左侧运算对象必须是非常量的左值,其结果也是左值
- 取地址符(&) 的运算对象是左值,返回指向该运算对象的指针,该指针是右值
- 内置解引用运算符,下标运算符,迭代器解引用运算符,string和vector的下标运算符 的结果都是左值
- decltype 作用于表达式时,如果表达式结果为左值,decltype将得到一个引用类型;否则也返回右值
基于范围的 for 循环(c++11)
#include <iostream>
int main(int argc, char** argv) {
using namespace std;
int array[]={1,2,3,4,5,6,7,6,5,4,3,2,1};
for(int i:array)//如果想修改数组,可把i设为引用for(int &i:array)
{
cout<<i<<endl;
}
return 0;
}
简单文件IO
#include<iostream>
#include<cstdlib>
#include<fstream>
#include<cstring>
int main(){
using namespace std;
ifstream readFile;
ofstream writeFile;
enum doWhat{readfile,writefile,exitProgram
};
int m;
cout<<"Read the file input 0,write the file input 1,finish the program input 2\n";
cin>>m;
while(1){
if(m==readfile){
cout<<"readFile : Please input the name of file\n";
char fileName[20];
cin>>fileName;
readFile.open(fileName);
if(!readFile.is_open()){
cout<<"Could not open the file"<<fileName<<endl;
cout<<"Program terminating.\n";
exit(EXIT_FAILURE);
}
char ch;
/*string theText;
readFile.getline>>theText;*/
cout<<"The text of file is : "<<endl;
while(readFile.get(ch))
cout<<ch;
if(readFile.eof()) cout<<"\nArrived the end of file\n";
else if(readFile.fail()) {
}
else if(readFile.bad()) cout<<"\nFile is bad\n";
readFile.close();
}else if(m==writefile){
cout<<"writeFile : Please input the name of file\n";
char fileName[20];
cin>>fileName;
writeFile.open(fileName);
cout<<"Please write the text you want input to file:\n"<<
fileName<<":\n";
char theText[100];
cin.get();//空行处理
cin.clear();
cin.get(theText,100);
writeFile<<theText;
writeFile.close();
}else if(m==exitProgram) break;
else cout<<"Please write 1 or 0 or 2 ! \n";
cout<<"Read the file input 0,open the file input 1,finish the program input 2\n";
cin>>m;
}
cout<<"OK Please write anything and Enter to exit ! ";
cin.clear();
cin.get();
cin.get();
return 0;
}
函数
- 在c++中,函数参数括号为空则与使用void作为参数等效。
- ANSI C 中函数参数括号为空表示不指出参数——即在后边定义参数列表。
c++ 中表示不指出参数应使用 …(省略号)return_type function(...);
不指出参数一般意味着要与可变参数的C函数(如printf())交互。 - 当用指针作为函数参数时,可用const表示函数不可通过指针改变指针所指内存的值。
- c++ 禁止将非const的指针赋给const的指针。
- 在C++中,当且仅当用于函数头或函数原型中,int *arr和int arr[]的含义才是相同的。在其他的环境下,二者的含义并不同,前者代表指向int类型的指针,后者代表数组名。p213
区分 const int*pt 与 int* const pt
;
const int*pt
表示指向常量。即不能通过该指针修改所指向内存的值。
int* const pt
表示常量指针。即不能修改该指针的值(不能再指向其他地方了)。
函数指针
函数名本身就是函数指针
声明函数指针
和声明函数一样,只是把函数名用(*pf)
代替:
int (*pf)(double x,int y);//声明了一个参数(double x,int y),返回值类型为int 的函数指针。
int (*p1)(double x,int y) = f1;//初始化
auto p1 = f2;//auto 自动类型推断 下面细说
使用函数指针
y = (*pf)(3.14,6);
//c++还允许:
y = pf(3.14,6);
函数指针数组
const double* (*pa[3])(const double *,int)={f1,f2,f3};
运算符[]的优先级高于 * ,因此*pa[3]
表示pa是一个包含3个指针的数组。
不能用 auto 自动类型判断,因为 auto 只适用于单值初始化。
指向函数指针数组的指针呢:
方法一:
auto pc = &pa;
方法二:
const double *(*(*pd)[3])(const double *,int) = &pa;
注意:
*pp[3] —— 包含三个指针元素的数组
(*pp)[3] —— 包含三个元素的数组,pp是指向该数组的指针
*(*pp)[3] —— 包含三个指针元素的数组,pp是指向该数组的指针
通过指向函数指针数组的指针进行函数调用:
(*pd)[i](p,6);
或: (*(*pd)[i])(p,6);
调用后返回指针所指的内存的值:
*(*pd)[i](p,6);
或:*(*(*pd)[i])(p,6);
typedef 进行简化
int (*pf)(double x,int y);//声明了一个参数(double x,int y),返回值类型为int 的函数指针。
typedef int (*p_func)(double x,int y);//声明了一个参数(double x,int y),返回值类型为int 的函数指针类型的别名:p_func
p_func pa[3] = {f1,f2,f3};
自动类型推断 auto
确保变量的类型与赋给它的初值的类型一致。即使你赋的初值类型不对。(见笔记一,基础与数据)
内联函数 inline
省去函数调用的处理时间,因而速度较快。
一般用于代码较短,本身处理就很快的函数。
inline int square(int x);
...
inlint int square (int x)
{
return x*x;
}
或直接:
inlint int square (int x){return x*x;}
注意:
- 仍然是按值传递参数(相比于 宏 的优点)
- 不可递归(不能在函数内部调用自己)
引用变量
int i = 12;
int & n =i;
引用变量 n 将是变量i 的别名,其值与地址都是一样的。
注意:
- 引用变量必须在声明时就初始化。
- 引用变量一旦被初始化,就一直“效忠”于该变量,不可更改。
- 不能引用表达式,除非是const引用(可初始化为一般表达式)
- 目前所说是左值引用,C+11中新增“右值引用”
主要用法——用于函数参数
对于既能用引用传递也能按值传递的函数,若参数较大(内存较大的对象或结构)时,用 const 引用参数更快些,否则就按值传递。
若引用参数是 const
时,在以下两种情况下会创建临时变量。
- 实参的类型正确,但不是左值
- 实参类型不正确,但能转换为正确类型
左值:是可被引用的数据对象,现在 变量和const变量都可视为左值。
非左值:包括字面常量,多项式等
一旦创建临时变量就和按值传递很像了,不会影响实参,是为了防止函数改变实参(因为引用参数是const的,本意就是为了防止改变实参)
指导原则p274
- 对于使用传递的值而不作修改的函数
- 如果数据对象很小,如内置数据类型或小型结构,则按值传递;
- 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针;
- 如果数据对象是较大的结构,则使用const指针或const引用,以提高程序的效率。这样可以节省复制结构所需的时间和空间;
- 如果数据对象是类对象,则使用const引用。传递类对象参数的标准方式是按引用传递。
- 对于修改调用函数中的数据的函数
- 如果数据对象是内置数据类型,则使用指针(引用也行);
- 如果数据对象是数组,则只能使用指针;
- 如果数据对象是结构,则使用引用或指针;
- 如果数据对象是类,则使用引用
const 引用
-
不可通过引用修改被引用对象的值,无论被引用对象是不是const 常量
-
const 引用可绑定到非常量对象,字面值常量,一般表达式,或者可自动转换的其他类型变量
double v =3.14; int i=2; const int &r0 = 3; const int &r1 = v; const int &r2 = i+1; //创建值为3的临时int量让 r1,r2 绑定 //所以只能是const引用
建议:尽可能使用 const
理由有三:
- 避免无意中修改数据的编程错误
- 可接受const与非const实参
- 可正确生成并使用临时变量
——尽可能将引用形参声明为const
返回引用
改程序只是演示了其本质,正确用法看第二个程序
#include"iostream"
using namespace std;
int & is0 (int& num);
int main()
{
int i=12;
cout<<"i : "<<i<<endl;
is0(i)++;
cout<<"i : "<<i<<endl;
getchar();
return 0;
}
int & is0(int& num)
{
if(num==0)
cout<<num<<"is 0 \n";
else
cout<<num<<"isn't 0\n";
return num;
}
结果:
i : 12
12isn't 0
i : 13
返回的仍是返回值所引用的变量。是左值(被引用的变量不是函数的临时变量)。
常规类型的返回是右值,因为它在临时内存中。
#include"iostream"
using namespace std;
const int & is0 (const int& num);
int main(int argc,char ** argv)
{
int i=12,n;
cout<<"i : "<<i<<endl;
n=is0(i);
cout<<"n : "<<i<<endl;
return 0;
}
const int & is0 (const int& num)
{
if(num==0)
cout<<num<<"is 0 \n";
else
cout<<num<<"isn't 0\n";
return num;
}
结果:
i : 12
12isn't 0
n : 12
优点:一般按值传递的返回,其实 是创建了临时的变量,而直接返回引用省略了这一步骤,更高效。
注意:
- 避免返回函数终止时不再存在的内存单元的引用。
方法一:返回一个作为参数传递给函数的引用
方法二:在函数中new一块内存,并返回指针,注意不要忘了delete该内存。
返回const引用——不能被修改的左值
编程习惯——避免模糊,比如使用const更加准确清晰。
另一种引用——右值引用&&
可引用右值
一般用于库设计人员能够提供有些操作的更有效实现。
默认参数
- 必须字声明函数原型时指出
- 只能从右往左指出默认参数
- 填实参时只能从左往右,不能跳过
- 函数定义则与一般定义一样,不用管默认值(也管不了)
int func(int x,int y=0);
函数重载
关键——函数特征标(参数列表)——参数类型,参数个数
这些不同就可以重载
- 调用时若没有与实参类型完全匹配的就会尝试使用 标准类型转换强制进行匹配。
- 但是若是转换强制匹配时发现有多个转换方式,就会出错。
- 将类型引用于类型本身视为同一个特征标。
- 不能只有返回类型不同而特征标相同。返回类型不同的同时特征标必须不同。
- 重载时优先调用最匹配的版本
函数模板
template <typename T>
void MySwap(T x,T y);
...
template <typename T>
void MySwap(T x,T y)
{
T t;
t=x;
x=y;
y=t;
}
- 关键字 template 和 typename(可替换为class)
- 将同一算法用于不同类型
- T 代表任一类型,编译器将会在具体情况时将其实例化
- 一般将模板放在头文件里
- 模板并不能缩短程序,最终还是要实例化的
- 可以有多个模板参数
emplate <typename T1,typename T2>
ADD(T1 x,T2 y);
重载模板
template<typename T>
void Swap(T &a, T &b);
template<typename T>
void Swap(T *a,T *b,int n);//void Swap(T a[],T b[],int n);
- 函数模板定义时可包含具体的类型
显式具体化
将模板函数的某些类型具体化,再定义。优先于一般模板。
第三代具体化:
- 对于给定的函数名,可以有非模板函数,模板函数和显式具体化模板函数以及他们的重载函数
- 非模板版本优先于显式具体化个模板版本,而显式具体化优先于模板生成的版本
- 显式具体化的原型及定义应以
template<>
打头
struct job
{
char name[40];
double salary;
int floor;
}
...
template<> void Swap<job>(job&,job&);
//Simple Form:
template<> void Swap(job&,job&);
显式实例化
template void Swap<int>(int,int);
或者在程序中使用函数来创建显式实例化
在需要类型自动转换时可以指定转换成哪种函数
//或者在语句中使用:
template <class T>
T Add(T a,T b)
{
return a+b;
}
...
int m=6;
double x = 12.2;
cout<<Add<double>(x,m)<<emdl;
注意——引用类型不能相互转换
int& 不能转换成double &或double
下面是错误的
int m = 5;
double x = 14.3;
↓↓↓↓↓ Error ↓↓↓↓↓
Swap<double>(m,x);
↑↑↑↑↑ Error ↑↑↑↑↑
编译器选择使用哪个函数的版本
对于单个参数,从最佳到最差的顺序如下:
- 完全匹配,但常规函数优于模板
- 提升转换(char→int,int→double)
- 标准转换(int→char,long→double)
- 用户定义的转换,如类声明中定义的转换。
…
具体规则:P289 ~ P294
模板函数中的判断类型
C++11 新关键字—— decltype
int x;
decltype(x) y;// y 和 x有相同的类型
可用于函数模板中自动判断返回类型
template<class T1,class T2>
void ft(T1 x,T2 y)
{
...
decltype(x+y) xpy = x +y;
...
}
对于decltype(expression) var
的规则:
- 若expression是未被括号括起的标识符,则var 与 expression 的类型一致
- 若 expression 是一个函数调用,则var是函数的返回类型(并不会真的调用函数)
- 若 expression 是括号括起来的标识符则是其类型的引用
- 否则 ,var 与 expression的类型一致
返回类型的判断(后置返回类型)(c++11)
auto ADD(T1 x,T2 y) -> decltype((x+y));
...
template <typename T1,typename T2>
auto ADD(T1 x,T2 y) -> decltype((x+y))
{
return (x+y);
}
内存模型
- 静态变量初始化
可以用表达式,函数返回确定静态变量的初始值 - C++11新增 constexpr 关键字用于创建常量表达式
- 静态外部变量将隐藏常规外部变量P114
- 静态局部变量只进行一次初始化
- 单定义规则:外部全局变量只能定义一次,初始化时可以
extern(可选)
,不初始化则默认为0,但可以多次声明(extern)
存储说明符和cv-限定符
存储说明符
- auto(c++11中不是,c++11中只用于自动类型推断,以前用于显式表示自动存储的变量)
- register(以前表示存储器变量,c++11中用于显式表示自动存储的变量)
- static
- extern
- thread_local(c++11新增)
- multable
thread_local
可与 static 和 extern 结合使用,指出变量的持续性与其所属线程的持续性相同(就像静态变量与程序的关系)。
multable
指出:即使结构(或类)变量为const,其某个成员也可以被修改
cv-限定符
- const
- volatile
volatile
:表明,即使程序代码没有对内存单元进行修改,其值也可能发生改变P317(一编译器会优化一些变量的处理,即使硬件上内存单元发生了改变,有时也不会被程序及时察觉,使用该关键词可避免该情况)
const
:对于const声明的常量,其链接性为内部的。这意味着可以在头文件中声明这种常量而不用担心重复定义。(c++中)
-
若希望某const常量的链接性为外部的,可以用 extern 覆盖其内部链接性。这时也需要在其他使用常量的文件中用 extern,但是此时该const常量只能初始化一次(就像一般的外部变量)
//file1.cpp extern const int bufSize = fcn(); //file1.h extern const int bufSize;
-
其作用域就像一般变量一样,对于同名const常量,局部可以隐藏外部
函数的链接性
一般默认为外部
- 可在声明原型和定义处用关键字static 表明是内部,这意味着函数只能在该文件内使用。其他文件也可以定义同名该函数,static的函数将会屏蔽外部同名函数。
- 非内联函数也符合单定义规则,链接性为外部的函数(一般函数)只能有一个定义。
- 内联函数不收单定义规则影响,意味着可将内联函数定义放在头文件,C++要求同一个内联函数的所有定义都必须相同。
语言链接性
c语言与C++的名称修饰不同,需要对C的预编译进行处理,详情见P319。
new 运算符
new 运算符的初始化
单值初始化
int *p = new int(6);
int *pi = new int{6};
多值(结构,数组)初始化(c++11 列表初始化)
int *p = new int[4]{1,2,3,4};
struct where{double x,double y,double z};
...
where *pw = new where{1.2,3.6,4.2};
定位new
需包含头文件 new
#include<new>
...
char * buffer1[40];
...
double *p = new (buffer1)double (12.88);
...
delete p;
名称空间
- 名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中 => 所以,默认名称空间中声明的变量链接性为外部的(除非它引用了常量)
using namespace std; //using编译指令
using std::cout;//using声明指令
尽量使用 using声明 而不用 using 编译指令,有重名时用using声明会报错,提示二义性,而using 编译指令将覆盖。
#include<iostream>
using namespace std;
namespace Jill
{
int iApple=2;
double dPencil;
}
int main()
{
//using Jill::iApple;//声明名称空间会与同名变量冲突,错误;
using namespace std;//using编译指令,不会与同名变量冲突,而是被局部的同名变量覆盖,但仍可用作用域解析符。
int iApple;
iApple=1;
cout<<Jill::iApple<<endl;//用作用域解析符使用名称空间中的变量
cout<<iApple;
return 0;
}
结果:
2
1
- 名称空间可嵌套
namespace elements
{
namespace fire
{
int flame;
...
}
float water;
}
...
使用 fire内部名称空间:
using namespace elements::fire;
- 可在名称空间中使用 using 编译指令和 using 声明
namespace myth
{
using Jill::iApple;
using namespace elements;
using std::cout;
using std::cin;
}
...
访问 iApple:
cout<<myth::iApple;
或
cout<<Jill::iApple;
- 名称空间是可传递的。上例中
using namespace myth;
相当于using namespace myth;
+using namespace Jill;
- 可以给名称空间创建别名:
namespace MT = myth;
//用于简化:
namespace FE = elements::fire;
using FE::flame;
- 未命名的名称空间
namespace
{
int count;
}
提供了链接性为内部的静态变量的替代品
放在文件中效果为 static int count;
名称空间使用指导
- 使用在已命名的名称空间中声明的变量,而不是使用外部全局变量
- 使用在已命名的名称空间中声明的变量,而不是使用静态全局变量
- 不要在头文件中使用 using 编译指令
- 导入名称首选 using声明 或 作用域解析符
- 对于 using 声明首