C++基础篇
输入输出流iostream
向流写入数据<<运算符
<<运算符接受两个运算对象,此运算符将给定的值写到给定的ostream对象中:
左侧:运算对象为ostream对象,如cout、cerr、clog
右侧:运算对象是要打印的值
输出结果:写入给定值的那个ostream对象,即此运算符返回其左侧的运算对象。
表达式等价于:(std::cout << "Enter two numbers:") << std::endl;
写入endl效果
添加一个换行符,然后结束当前行,并将与设备关联的缓冲区(buffer)中的内容刷到设备中显示。缓冲刷新操作可以保证到目前为止程序所产生的所有输出都真正写入输出流中,而不是仅停留在内存中等待写入流。
从流读取数据>>运算符
从给定的istream读入数据,并存入给定的对象中:
左侧:运算对象为istream对象,如cin
右侧:运算对象是一个对象
输出结果:运算符返回其左侧的运算对象。
表达式等价于:(std::cin >> v1) >> v2;
作为条件处理
while(std::cin >> value)
其效果是检测流的状态。
True:流是有效的,即流未遇到错误,那么检测成功。
False:当遇到文件结束符(end-of-file),或遇到一个无效输入时(例如读入的值不是一个整数),istream对象的状态会变为无效。
行读入
解释:有时候希望得到的字符串中保留空白符,则可以使用getline函数代替原来的>>运算符。
#include<sstream>
string line;
//每次读入一整行,直到文件末尾
while (getline(cin, line))
cout << line << endl;
变量
初始化与赋值
初始化不是赋值,其含义是创建变量是赋予其一个初始值,而赋值的含义是值替换。
注意:
long double id = 3.1415926536;
int a{id},b = {id}; //错误
int c(id),d = id; //正确
如果使用列表初始化且初始值存在丢失信息的风险,则编译器报错。
变量声明和定义的关系
声明:使得名字为程序所知。一个文件如果想要使用别的地方定义的名字则必须写声明。
定义:负责创建与名字关联的实体。申请了储存空间,赋予初始值。
作用:
如果想要在多个文件中使用同一变量,就必须将声明和定义分离。变量的定义必须出现在且只能出现在一个文件中,而其他用到该变量的文件则对其声明,同时不能有重复定义。
声明关键字extern
extern int i; //声明i
int j; //定义,默认初始值
extern double pi = 3.1416; //赋值则为定义
使用extern和包含头文件来引用函数有什么区别?
1、extern的引用方式比包含头文件要简洁,extern的使用方法很是直接,想引用哪个函数就用extern声明哪个函数。
2、会加速程序的编译(预处理)的过程,节省时间。在大型C程序编译过程中,这种差异是非常明显的。
引用(reference)与指针(pointer)
引用 &
int ival = 1024;
int &refVal = ival;
含义:为对象起另外一个名字,引用类型引用另外一种类型。
解释:定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。
性质:
1、引用必须初始化:初始值对象绑定。
2、引用初始化完成,则不能修改:已经完成绑定,无法重新绑定。
3、引用即初始化对象起一个别名,不会额外开辟空间:引用不是一个对象,没有开辟空间。
指针 *
int ival = 1024;
int *p = &ival; //&获取地址
*p = 42; //*解引用
含义:指针存放的是某个对象的地址(32位系统通常是4字节,64位系统通常是8字节)。需要获取地址,可以使用取地址符&;需要获取对象,可以使用解引用符*
空指针(null pointer)与野指针
空指针:赋值为0或者NULL,没有指向任何对象。c++11最好使用nullptr。
野指针:指针变量指向非法的内存空间。
void*指针
特殊的指针类型,可以存放任意的对象地址。
限制:
一般拿它和别的指针比较,作为函数输入输出,或者赋值给另一个void*指针。不能直接操作void*指针所指的对象,因为不知道是什么类型,无法确定能做哪些操作。
指针和数组
联系:使用数组的时候编译器一般会把它转换为指针。数组中每个元素也是对象,可获取地址。
特性:对于数组名字,编译器都会自动地将其替换为一个指向数组首元素的的指针(32位-4字节、64位-8字节),使用数组对象其实就是使用一个指向数组首元素的指针。
注意:使用关键字decltype则不会发生上述的替换。
int nums[] = { 0,1,2,3,3 };
int* p = nums; //等价于p = &nums[0];
auto ia(nums); //ia是一个整形指针int* ia
decltype(nums) ib; //ib是一个数组int ib[5]
数组begin与end(c++11)
begin():获取首元素的指针。
end():获取尾元素的下一个位置的指针。
int* b = begin(nums);
int* e = end(nums);
while (b != e)
cout << *b++ << endl;
引用与指针区别
1、指针无需一定初始化,可以为NULL;引用必须初始化,总会指向一个对象。
2、指针定义会开辟一个地址空间;引用不会额外开辟空间。
3、指针本身是个对象,允许赋值和拷贝,生命周期内可以指向不同对象;引用初始化完成后便绑定,无法更换绑定。
const限定符
定义一种变量,它的值无法被修改。编译器在编译的过程中把用到该变量的地方替换成对应的值。
const int bufSize = 512;
指向常量的指针(pointer to const)
内容:不能通过该指针改变对象的值。但没有规定那个对象的值不能通过其他途径改变。
const double pi = 3.14;
const double *cptr = π
double dval = 3.15;
cptr = &dval; //指针常量可以指向一个非常量对象
dval = 3.14; //cptr指向对象的值被改变了
常量指针(const pointer)
内容:指针是对象,把*放在const关键字之前,把指针定为一个常量。必须初始化,初始化完成后,则指针不能重新指向新的对象(即存放的那个地址值无法修改)。
int errNumb = 0;
int *const curErr = &errNumb;
顶层const与底层const
顶层const:表示指针本身是个常量,即常量指针。更一般表示任意对象是常量。
底层const:表示指针所指的对象是一个常量,即指针常量。
int top = 0;
int* const top1 = ⊤ //不允许改变top1的值,是一个顶层const
const int top2 = top; //不允许改变top2的值,是一个顶层const
const int* low = ⊤ //允许改变low的值,是一个底层const
constexpr和常量表达式(c++11)
内容:
1、常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。
2、constexpr主要用于那些值在编译时就能确定的场合。它用于强调并利用编译时常量的特性。const则只是表明上只读,运行时一样可以通过其他方式改变。
3、而constexpr函数的返回值也必须是常量并且在编译时必须可以计算出来。
constexpr int mf = 20;
constexpr int limit = mf +1;
constexpr int sz = size(); //size是一个constexpr函数
constexpr int sqr1(int a){
return a*a;
}
const int sqr2(int a){
return a*a;
}
int main()
{
array<int,sqr1(10)> mylist1; //可以
array<int,sqr2(10)> mylist2; //不可以
return 0;
}
constexpr和指针
内容:constexpr仅仅对指针有效,与指针所指的对象无关。constexpr会把所定义的对象置为顶层const。
int a = 10;
const int* p = nullptr; //p是一个指针常量,可以改指向
constexpr int* q = nullptr; //q是一个常量指针,不可改指向
p = &a;
//q = &a;//错误
*q = a;
mutable
用来修饰属性的,表示可变,被mutable修饰的属性,可以在常函数中修改,也可以由常对象修改
处理类型
别名
typedef int *pst;
using Int = int; //c++11
指针、常量和类型别名
注意:
typedef int *pst;
const pst cstr = nullptr;//cstr是指向int的常量指针,不可修改指向
//cstr = &a; //错误
*cstr = 10;
//不等于
const int *cstr = nullptr;
解释:这种理解是错误的。声明语句中用到pst时,其基本数据类型是指针。可是用int*重写了声明语句后,数据类型就变成了int,*成为了声明符的一部分。这样改写的结果是,const int成了基本数据类型。前后两种声明含义截然不同,前者声明了一个指向int的常量指针,改写后的形式则声明了一个指向const int的指针。
auto类型(c++11)
内容:auto让编译器通过初始值来推算变量的类型。一条声明语句只有一个基本类型。
auto i = 0,*p = &a; //int
decltype类型(c++11)
内容:有时候希望从表达式的类型推算出要定义的变量的类型,但是不想用其表达式的值初始化变量。而decltype的作用就是选择并返回操作数的数据类型。
注意:decltype((decl))双层括号的结果永远是引用类型。或者表达式内容是解引用操作,结果也是引用类型。
int decl = 50;
decltype(decl) a1 = 100; //int
decltype((decl)) a1 = decl; //int&
decltype(*p) a1 = decl; //int&
左值和右值
左值(lvalue)
出现在赋值语句的左边的表达式,其求值结果是一个对象或者一个函数,有确定的内存地址,代表内存中有值的表达式。即指可寻址的非只读表达式。
右值(rvalue)
指的是可以出现在赋值语句右边的表达式,有时也被称为临时值。
简单归纳
当一个对象被作用右值的时候,用的是对象的值(内容);当一个对象被用作左值的时候,用的是对象的身份(在内存中的位置)。
sizeof运算符
sizeof运算符返回一条表达式或者一个类型名字所占的字节数。而且sizeof不会实际求运算对象的值,即使是无效指针也不会有什么影响。
Sales_data data, * p;
cout << sizeof(Sales_data) << endl; //对象类型大小
cout << sizeof data << endl; //对象大小
cout << sizeof p << endl; //指针大小
cout << sizeof *p << endl; //对象大小
cout << sizeof data.name << endl; //对象成员大小
注意:
1、对数组执行sizeof运算获取的是整个数组空间的大小,sizeof运算不会把数组转换成指针来处理。
2、对string对象或vector对象执行sizeof运算只返回该类型的固定部分大小,不计算对象中的元素占用了多少空间。
int sum[] = { 0,1,2,3,4 };
cout << sizeof sum << endl; //20,并没有是指针大小8(64位)
vector<int> vec(5, 10);
cout << sizeof vec << endl; //32
vector<int> vec1(50, 10);
cout << sizeof vec1 << endl; //32
范围for语句(c++11)
范围for语句可以遍历容器或其他序列的所有元素。这些类型的序列的共同特点就是拥有能返回迭代器的begin和end成员。
vector<int> v = {0,1,2,3,4,5};
for(auto &r : v)
r *= 2;
for(auto beg = v.begin(),end = v.end() ; beg != end; ++beg)
{
auto &r = *beg;
r *= 2;
}
注意:
不能通过范围for语句增加vector对象(或者其他容器)的元素,因为上述代码等同于第二段for语句代码,已经预存了end()的值,再进行改变则变的无效。
try语句块和异常处理
异常是指存在于运行时的反常行为,这些行为超出了函数正常功能的执行范围。而当程序的某部分检测到一个它无法处理的问题时,需要用到异常处理。
异常处理包括
throw 表达式(throw expression):异常检测部分使用throw表达式来表示它遇到了无法处理的问题。我们说throw引发了异常。
try 语句块(try block):try语句块用来处理异常,try语句块以关键字try开始,以一个或多个catch子句结束,try语句块中代码抛出的异常通常会被某个catch子句处理。
一套异常类(exception class):用于在throw表达式和相关的catch子句之间传递异常的具体信息。
throw
程序的异常检测部分使用throw表达式引发一个异常。throw表达式包含关键字throw和其后的一个表达式,其中表达式的类型就是抛出的异常处理。抛出异常将终止当前的函数,并把控制权转移给能处理该异常的代码。
try语句块
exception-declaration为异常声明,选中某个catch子句处理异常之后,执行与之对应的块。
try {
program-statements
} catch (exception-declaration) {
handler-statements
} catch (exception-declaration) {
handler-statements
} ...
处理代码
what是异常类型class中的一个成员函数,返回的是初始化一个具体对象时所用的string对象的副本
try
{
if (sizeof(sum) < 30)
throw runtime_error("运行时错误处理");
else if (sizeof(sum) > 40)
throw logic_error("逻辑错误处理");
}
catch (runtime_error err)
{
// <30则进入该catch语句进行异常处理,打印 运行时错误处理
cout << err.what();
}
catch(logic_error err)
{
// >40则进入该catch语句进行异常处理,打印 逻辑错误处理
cout << err.what();
}
标准异常
1、exception头文件:定义了最通用的异常类exception,只报告异常的发生,不提供任何额外信息。
2、stdexcept头文件:定义了几种常用的异常类。
3、new头文件:定义了bad_alloc异常类型。
4、type_info头文件:定义了bad_cast异常类型。
stdexcept头文件
exception--------------最常见的问题
runtime_error----------只在运行时才能检测出问题
range_error------------运行时错误:生成的结果超出了有意义的值域范围
overflow_error---------运行时错误:计算上溢
underflow_error--------运行时错误:计算下溢
logic_error--------------程序逻辑错误
domain_error-----------逻辑错误:参数对应结果值不存在
invailid_argument------逻辑错误:无效参数
length_error------------逻辑错误:试图创建一个超出该类型最大长度的对象
out_of_range-----------逻辑错误:使用一个超出有效范围的值
内联函数
函数调用的开销
1、调用函数一般比求等价表达式的值要慢一些,因为一次函数调用包含着一系列的工作。
比如:调用前要保存寄存器,并在返回时恢复;实参的拷贝操作;控制权转移,程序转向一个新的位置继续执行。
内联函数可以避免函数调用开销
内联函数(inline):将函数指定为内联函数,就是编译过程中将它在每个调用点上进行“内联地”展开。
inline int reMax(int a, int b)
{
return a > b ? a : b;
}
int main()
{
cout << reMax(10,20) << endl;
return 0;
}
适用:一般用于优化规模较小、流程直接、频繁调用的函数。很多编译器不支持内联递归函数。
友元
类的主要特点之一是数据隐藏,即类的私有成员无法在类的外部(作用域之外)访问,但是,有时候需要在类的外部访问类的私有成员,怎么办?
解决方法是使用友元函数,友元函数是一种特权函数, c++允许这个特权函数访问私有成员。
这一点从现实生活中也可以很好的理解:比如你的家,有客厅,有你的卧室,那么你的客厅是ublic的,所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去,但是呢,你也可以允许你的闺蜜好基友进去。程序员可以把一个全局函数、某个类中的成员函数、甚至整个类声明为友元。
持续更新中