C++变量的存储类别与作用域
最后更新于:2022-04-01 09:39:15
## 总结一下C++中变量的存储类别以及变量的作用域。
(1)标示符的存储类别决定了标示符在内存中存在的时间(我们可以理解标示符就是确定一个变量的符号,也就是我们所说的变量名)
## 二:存储类别
(1)静态存储类别:静态存数类别变量(我们简称静态变量),从程序的开始处就存在,其生命期伴随整个程序。
(2)自动存储类别:当变量时自动存储类别时,变量在进入到定义它们的程序快时定义它,在离开它们所在的程序块(作用域)时销毁它,因此成为自动变量。其中关键字auto和register用来声明自动类型的变量,
## 三:自动存储类型:
(1) 只有在函数中的局部变量才是自动存储类型的,我们在MAIN函数中定义的变量一般都是自动变量,关键字auto可以不写,默认情况局部变量就是自动变量(我们把自动存储类别的变量叫自动变量)如下图:
~~~
int a;
int b;
~~~
都是自动变量
(2)自动变量还有一种寄存器变量,既使用关键字register,我们把经常被调用的变量声明为寄存器变量,顾名思义,让变量存在于寄存器中,达到更加快速的存储,这个我们了解即可,现在很多编译器带有可以识别频繁使用变量的功能,不需要程序员手动设置。
根据最小特权原则,我们应该定义自动变量。代码应该只被赋予它仅能完成设计任务的权限,无需更多权限。
## 四:静态存储类别
(1)关键字extern和static用于静态存储类别的变量,静态存数类别变量(我们简称静态变量),从程序的开始处就存在,其生命期伴随整个程序,一直存在程序的执行过程,但是:即便是静态变量,也并不意味着这些变量(专业来说叫:标识符),在整个程序都是能用的(或者说是可以访问的),这一点在下面有专门的实例。
(2)对于静态变量,许多被误认为和全局变量一致,这一点是错误的**,static变量仅仅在变量的作用范围内可见,而全局变量是在所有地方都可见的,这就是static变量与全局变量的区别,**我们要纠正一点:存储类别(存数类型)和变量的作用域是两个独立的问题。
我们应该遵循最小特权原则,不去使用全局变量。
(3)我们解释一下,全局变量和静态变量的区别:全局变量是不显示用static修饰的全局变量,但全局变量默认是静态的,作用域是整个工程,在一个文件内定义的全局变量,在另一个文件中,通过extern 全局变量名的声明,就可以使用全局变量。
全局静态变量是显示用static修饰的全局变量,作用域是所在的文件,其他的文件即使用extern声明也不能使用。
## 五:标识符(变量)的作用域
(1)程序中可以使用标识符的范围成为标识符 的作用域,其中我们称标识符为变量,更加专业。例如:我们在一个语句块中声明了一个局部的变量,那么只能在该语句块中使用该变量。
(2)其中作用域大致分为四个:
全局作用域:此变量对于从其声明处到文件结束,都是已知的。
局部作用域:一个语句块中声明的变量,其作用域在于该语句块中。
函数原型作用域:就是函数的形参列表中。
这里着重标记一下局部作用域:当语句块是嵌套的,并且外层语句块中的一个标识符和内层语句块中的标识符名字一致时,外层语句块中的标识符被隐藏,直到内层语句块结束。
## 六 注意:
(1)我们应该避免使用全局变量,使用全局变量可能在不需要访问变量时却错误的访问变量,违反最小特权原则。仅用于特定函数中的变量应该声明为那个函数的局部变量,而不是全局变量。
(2)应该避免使用会隐藏在外部作用域中名称的变量名,这可以通过在程序中避免使用相同的标识符。
上述总结说明了变量的类型以及作用域,此时我们举个程序,列举上述出现的情况。如下:
~~~
#include<iostream>
using namespace std;
void use_Global(); //声明函数
void use_Local();
void use_Static_Local();
int a=1; //在函数外定义一个全局的变量
int main()
{
cout<<"the value of global a in the main function is "<<a<<endl;
//<span style="color:#ff0000;">输出全局变量的值,值为 1</span>
int a=10; //在MAIN函数中定义一个同名字的局部变量
cout<<"now ,in the main function , local a is "<<a<<endl;
//<span style="color:#ff0000;">输出局部变量,此时全局变量被隐藏,此时的输出的值为 10</span>
{ //在语句块中定义局部变量
int a=7;
cout<<"int the scope ,local a is "<<a<<endl; //<span style="color:#ff0000;">输出值为:7</span>
}
cout<<"local a in the main's outer scope is"<<a<<endl;
//<span style="color:#ff0000;">脱离语句块,输出变量,输出值为:10</span>
use_Local(); //使用函数,函数中定义一个局部变量
use_Static_Local(); //使用函数,函数中定义一个静态局部变量
use_Global(); //使用函数,函数中使用全局变量
use_Local();
use_Static_Local();
use_Global();
return 0;
}
void use_Global()
{
cout<<"\n global a is "<<a<<endl;//<span style="color:#ff0000;">此作用域中,没有定义同名的标识符,所以输出的全局变量,值为10</span>
cout<<"enter the use_Global function "<<endl;
a++;
cout<<"\n global a is "<<a<<" on the exit "<<endl;
}
void use_Local()
{
int a=20;
cout<<"enter the function use_Local, the a is "<<a<<endl;
a++;;
cout<<"exit the function use_Local, the a is "<<a<<endl;
}
void use_Static_Local()
{
static int a=5; //定义静态变量,函数调用结束后,仍存在,但是仅在此函数中可以调用,脱离此作用域,就不行。
cout<<"enter the function use_Static_Local, the a is "<<a<<endl;
a++;;
cout<<"exit the function use_Static_Local, the a is "<<a<<endl;
}
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-17_56c446a9deea4.jpg)
C++ this指针详解
最后更新于:2022-04-01 09:39:13
## C++this指针操作
在这里总结一下this 指针的相关知识点。
首先,我们都知道类的成员函数可以访问类的数据(限定符只是限定于类外的一些操作,类内的一切对于成员函数来说都是透明的),那么成员函数如何知道哪个对象的数据成员要被操作呢,原因在于每个对象都拥有一个指针:this指针,通过this指针来访问自己的地址。注:this指针并不是对象的一部分,this指针所占的内存大小是不会反应在sizeof操作符上的。this指针的类型取决于使用this指针的成员函数类型以及对象类型,(1)**假如this指针所在类的类型是Stu_Info_Mange类型,(下面的测试用例中的类的类型,借来用用),并且如果成员函数是非常量的,则this的类型是:Stu_Info_Mange * const 类型**,(2)**既一个指向非const Stu_Info_Mange对象的常量(const)指针,假若成员函数是常量类型,则this指针的类型是一个指向constStu_Info_Mange对象的常量(const)指针。有点绕吧,仔细理解。**
## 二:this指针常用概念
this只能在成员函数中使用。全局函数,静态函数都不能使用this。实际上,成员函数默认第一个参数为T* const register this。
为什么this指针不能再静态函数中使用?
大家可以这样理解,静态函数如同静态变量一样,他不属于具体的哪一个对象,静态函数表示了整个类范围意义上的信息,而this指针却实实在在的对应一个对象,所以this指针当然不能被静态函数使用了,同理,全局函数也一样,我是这样理解的,不知道大家怎样理解,大家可以评论交流下。
(1):this指针是什么时候创建的?
this在成员函数的开始执行前构造的,在成员的执行结束后清除。
(2)this指针如何传递给类中函数的?绑定?还是在函数参数的首参数就是this指针.那么this指针又是如何找到类实例后函数的?
this是通过函数参数的首参数来传递的。this指针是在调用之前生成的。类实例后的函数,没有这个说法。类在实例化时,只分配类中的变量空间,并没有为函数分配空间。自从类的函数定义完成后,它就在那儿,不会跑的。
(3)this指针只有在成员函数中才有定义。因此,你获得一个对象后,也不能通过对象使用this指针。所以,我们也无法知道一个对象的this指针的位置(只有在成员函数里才有this指针的位置)。当然,在成员函数里,你是可以知道this指针的位置的(可以&this获得),也可以直接使用的。
## 三:this指针的使用:
一种情况就是,在类的非静态成员函数中返回类对象本身的时候,我们可以使用圆点运算符(*this).,箭头运算符this->,另外,我们也可以返回关于*this的引用,这样我们可以像输入输出流那样进行“级联”操作。
文字理解起来很枯燥,不如例子直接,我们下面就举几个实用的例子来分析。
我们建立一个学生信息类,具体东西注释很清晰了,直接上代码、
~~~
#include<iostream>
#include<string>
using namespace std;
class Stu_Info_Mange
{
int sno;
string sname;
int age;
int grade;
public:
Stu_Info_Mange(int s=0,string n="",int a=0,int g=0)
{
sno=s;
sname=n;
age=a;
grade=g;
}
void Setsname(int sn) //使用this指针进行赋值
{
this->sname=sn;
}
int Setage(int a)
{
this->age=a;
return (*this).age; //使用this指针返回该对象的年龄
}
void print()
{
cout<<"the sname is "<<this->sname<<endl; //显式this指针通过箭头操作符访问
cout<<"the sno is "<<sno<<endl;//隐式使用this指针打印
cout<<"the age is "<<(*this).age<<endl;//显式使用this指针通过远点操作符
cout<<"the grade is "<<this->grade<<endl<<endl;
}
};
int main()
{
Stu_Info_Mange sim1(761,"张三",19,3);
sim1.print(); //输出信息
sim1.Setage(12); //使用this指针修改年龄
sim1.print(); //再次输出
return 0;
}
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-17_56c446a9b4a8c.jpg)
以上的例子中,我们要设置一个学生的信息,需要对每一个相关变量所属的成员函数进行调用(名字调用设置名字的成员函数。。。),我们还可以使用this的另外一个功能,让他实现级联,方便我们调用,对此,我们对上述代码进行修改,如下:
~~~
#include<iostream>
#include<string>
using namespace std;
class Stu_Info_Mange
{
int sno;
string sname;
int age;
int grade;
public:
Stu_Info_Mange(int s=0,string n="",int a=0,int g=0)
{
sno=s;
sname=n;
age=a;
grade=g;
}
<span style="color:#ff0000;">Stu_Info_Mange &</span> Setsname(string s) //<span style="color:#ff0000;">所有的相关函数,都返回该对象的引用,这样就可以实现级联,方便编码</span>
{
this->sname=s;
<span style="color:#ff0000;">return (*this);</span>
}
<span style="color:#ff0000;">Stu_Info_Mange &</span> Setsno(int sno)
{
this->sno=sno;
<span style="color:#ff0000;">return *this</span>;
}
<span style="color:#ff0000;">Stu_Info_Mange &Setgrade(</span>int grade)
{
this->grade=grade;
<span style="color:#ff0000;">return *this;</span>
}
<span style="color:#ff0000;">Stu_Info_Mange &</span>Setage(int age)
{
this->age=age;
<span style="color:#ff0000;">return *this;</span>
}
void print()
{
cout<<"the sname is "<<this->sname<<endl;
cout<<"the sno is "<<this->sno<<endl;
cout<<"the age is "<<this->age<<endl;
cout<<"the grade is "<<this->grade<<endl<<endl;
}
};
int main()
{
Stu_Info_Mange sim;// 使用默认参数
<span style="color:#ff0000;">sim.Setsname("张三").Setsno(457).Setgrade(2012).Setage(20); //级联</span>
//使用this指针使串联的函数调用成为可能
sim.print();
return 0;
}
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-17_56c446a9c636d.jpg)
关于this指针的一些概念就总结到这里吧,现在抓紧攻读C++primer,有新东西再完善吧![奋斗](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-17_56c446a99dec4.gif)
![奋斗](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-17_56c446a99dec4.gif)
。
C++函数的参数传递机制以及参数的类型选择
最后更新于:2022-04-01 09:39:11
## C++primer之函数的参数传递以及参数的类型
## 一:函数的基本知识
(1)函数要素:返回类型,函数名字,形参(参数之间用逗号隔开)
(2)函数调用机制:我们通过调用运算符来执行函数,其中运算符就是括号
(3)当我们调用函数时,主调函数被暂停执行,被调函数开始执行,当被调函数遇到return语句时,return语句完成两项工作,1:返回return语句中的值。2:将控制权从被调函数转移到主调函数。函数的返回值用于初始化调用表达式的结果。
(4)函数的实参和形参必须类型一致,或者实参可以通过隐式转换到形参类型
下面举个例子:
~~~
#include<iostream>
using namespace std;
int function(int a) //函数返回类型,名字,参数
{ //函数体
int c=1;
while(a!=1)
{
c=c*(a--);
}
return c; //返回值
}
int main ()
{
cout<<function(5)<<endl; //调用运算符()执行函数
return 0;
}
~~~
运行结果是:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-17_56c446a93f963.jpg)
谈到函数中的参数,他们是局部对象,既生命期就是在函数开始执行到函数结束执行期间(生命期:就是对象在程序中存在的时间),形参和定义在函数中的变量都是局部变量,同时,局部变量会隐藏外层作用域中的同名的变量。
下面举个例子:
~~~
//局部静态变量
#include<iostream>
using namespace std;
int Increase()
{
static int a;
a++;
return a;
}
//与局部变量的区别在于: 在函数退出时,
//这个变量始终存在,静态局部变量延长了局部变量的生命周期.
//但不能被其它 函数使用, 当再次进入该函数时, 将保存上次的结果。
//其它的特点与局部变量一样。
int main()
{
for(int i=0;i<5;i++)
cout<<"After the function,the result is "<<Increase()<<endl;;
//函数调用结束,该变量仍然存在,没有被销毁,继续累加,但是由于是局部的,只能在函数内被访问。
return 0;
}
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-17_56c446a951457.jpg)
## 二:参数传递
(1)传值调用,实参通过拷贝给形参,形参和实参是两个相互独立的对象,两者之间互相不影响。
(2)传引用调用(主推使用,好处多多):形参是引用类型,即当函数被调用时,形参就是实参的另一个名字,函数中对形参的操纵就是对实参本身的操作(够通俗吧)![大笑](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21abc5518.gif)
![大笑](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21abc5518.gif)
(3)指针形参,当执行指针拷贝时,以指针参数传递,指针形参:此时将复制实参的指针:形参的改变不会引起实参的改变,但是形参指向的内容可以发生变化。
文字太乏味,举个例子大家就知道了。
~~~
//三种参数传递传递
#include<iostream>
using namespace std;
int Increase1(int a) //<span style="color:#ff0000;">拷贝的值传递</span>
{
a++;
return a;
}
int Increase2( int &a) //<span style="color:#ff0000;">引用参数传递</span>
{
a++;
return a;
}
int Increase3( int *p) //<span style="color:#ff0000;">指针参数传递</span>
{
(*p)++;
return (*p);
}
int main()
{
int c=5;
Increase1(c);
cout<<"After the Increase1,the result is "<<c<<endl;
//以拷贝的方式进行调用,c的值不会变,
Increase2(c);
cout<<"After the Increase2,the result is "<<c<<endl;
//以引用的形式进行调用,c的值发生改变。
Increase3(&c);
cout<<"After the Increase3,the result is "<<c<<endl;
//以指针参数传递,指针形参:此时将复制实参的指针:形参的改变不会引起实参的改变,但是形参指向的内容可以发生变化。
return 0;
}
~~~
运行截图是:![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-17_56c446a96535c.jpg)
我们建议使用,引用类型的形参代替指针(指针还是用在函数外边好).
为什么使用引用参数呢?
我们应该避开那些拷贝传递,拷贝传递费时费力,占资源,使用引用,我们就是直接在对象上操作,不需要拷贝,指针参数也是一样。无论任何是时候,我们都尽量不适用“传值调用”。
使用引用类型的形参还有一个好处就是,我们要从一个函数中得到多个值,而不是单单一个。举个例子,有一字符串,里面有很多单词,要求,计算出第一个第一个空格(也就是第一个单词后的空格),和所有空格数量,此时要计算出两个值,则我们可以如下:
~~~
#include<iostream>
using namespace std;
#include <string>
int find_char(<span style="color:#ff0000;">const string s,int &a</span>)
{
cout<<"the string is "<<s<<endl;
decltype(s.size()) i=0; //i 的类型个s.size 一样
i=s.find(' ',0); //从索引0开始找第一个为空格的,返回索引,失败返回-1
for(auto b=s.begin();b!=s.end();b++) //使用C++迭代器查找string中总的空格数
{
if(*b==' ')
<span style="color:#ff0000;">a++; //这个引用参数虽然没有输出,但是我们仍然可以得到它,这就是引用的好处</span>
}
return i;
}
int main()
{
string s="Zheng zhengzhou university software institute";
int k=0;
cout<<"the blank characters of first index is "<<find_char(s,k)<<endl;
cout<<"the sum of black characters have "<<k<<endl; //引用形参,形参改变,实参也跟着改变。
return 0; //<span style="color:#ff0000;">实现了一个返回值,却可以得到两个有用的参数</span>
}
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-17_56c446a974ab8.jpg)
## 三:初始化时忽略形参的const特性
我们使用实参初始化形参时,可以忽略形参的顶层const,通俗一点来说,当形参是const类型,实参是否是const 类型都一样。反之也成立(当实参是常量类型,形参是否是常量类型都一样),只是当形参是常量类型,函数中不允许改编实参。
例子如下:
~~~
//实参初始化形参时,可以忽略形参是否是const类型
#include<iostream>
using namespace std;
int add1(const int &a)
{
cout<<a<<endl;
return 1;
}
int add2(int a,int b)
{
cout<<"After the add2,the result is "<<a+b<<endl;
return 1;
}
int main()
{
const int a=9;
int b=10;
add1(a); //形参是常量,实参是非常量,只要在程序中不改变形参的值即可:
add1(b); //使用const实参初始化const形参肯定是没有问题的。
const int c=11;
const int d=12;
add2(c,d); //形参是非常量,实参是常量。
return 0;
}
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-17_56c446a98d543.jpg)
上面的例子告诉我们,尽量使用常量的引用,当在函数中不改变参数时,我们设置为常量。把函数不会改变的形参定义成普通的引用是一种常见的错误,这么做带给函数的调用者一种误导,既函数可以修改它的实参值,使用引用而非常量引用也会极大地限制所能接受的实参类型,例如,我们不能把const 对象,传给普通的引用形参。
## 四:形参是数组
当形参是数组时候,若形参是:a[10],a,a[],这三种形式都一样,原理是:数组会被转换成指针,我们外函数传递数组,其实就是为数组传递数组的首元素的指针。
例如一下程序:
~~~
#include<iostream> //显式的传入一个数组大小的参数
using namespace std;
int print(const char *a,int b) //数组作为形参的,传递的其实是手首元素指针,其中参数1也可以写成:a[],a[10]
{
for(int i=0;i<b;i++)
cout<<a[i]<<endl;
return 0;
}
int main()
{
char a[]="helloworld";
//重载函数,编译器根据参数的类型个数判断调用哪个
print(a,10); //其实有11个元素,最后一个元素为空
return 0;
}
~~~
## 五:函数的返回类型
(1)存在返回值,返回值类型与函数返回类型一致,或者可以隐式转换,返回值通过return 返回到主调函数中。其中,一个返回引用的的函数可以充当左值,其原理和变量可以充当左值是一样的。
(2)没有返回值,既renturn ;即可,也可以没有这句话,函数会自动隐式添加这句话。
举个例子
~~~
<span style="font-size:18px;">//返回类型是引用可以当左值
#include<iostream>
using namespace std;
int& function(int &a)
{
cout<<"Before the change,the a is"<<a<<endl;
return a;
}
int main()
{
int a=4;
function(a)=50;
cout<<"after the change,the a is"<<a<<endl;
return 0;
}</span>
~~~
第一遍看C++primer,以后还得多看,多想,多练。暂且到这吧![奋斗](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-17_56c446a99dec4.gif)
![奋斗](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-17_56c446a99dec4.gif)
C++异常机制知识点
最后更新于:2022-04-01 09:39:09
**在这里总结一下,C++中的异常机制,以及如何使用异常的知识点**
C++中处理异常的过程是这样的:在执行程序发生异常,可以不在本函数中处理,而是抛出一个错误信息,把它传递给上一级的函数来解决,上一级解决不了,再传给其上一级,由其上一级处理。如此逐级上传,直到最高一级还无法处理的话,运行系统会自动调用系统函数terminate,由它调用abort终止程序。这样的异常处理方法使得异常引发和处理机制分离,而不在同一个函数中处理。这使得底层函数只需要解决实际的任务,而不必过多考虑对异常的处理,而把异常处理的任务交给上一层函数去处理。
总结C++异常机制的用法概念
首先异常机制中最重要的三个关键字就是:throw try catch。
Throw抛出异常,try 包含异常模块,catch 捕捉抛出的异常,三者各有各的分工,集成在一起就构成了异常的基本机制。
**我们为什么要检测异常,抛出异常,?**
程序做错误检查是必要的,通常我们可以通过返回值告诉客户有了错误,不过异常提供了更加方便的手段和丰富的信息。举个例子,一个游戏软件,需要判断你在打游戏中选择的关口(类似于地下城勇士中勇士级,王者级等![大笑](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21abc5518.gif)
![大笑](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21abc5518.gif)
![大笑](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-19_569e21abc5518.gif)
),其中每一个关口都对应这一个ID(这是必须的),当你等级不够时,不能选择高等级的,此时就要抛出一个异常,提示你等级不够,不可以进入,例子可能有些不恰当,但足以说明使用异常处理的重要性。废话不说了,下面详细介绍下如何使用异常处理。
如果在try语句块的程序段中(包括在其中调用的函数)发现了异常,且抛弃了该异常,则这个异常就可以被try语句块后的某个catch语句所捕获并处理,捕获和处理的条件是被抛弃的异常的类型与catch语句的异常类型相匹配。由于C++使用数据类型来区分不同的异常,因此在判断异常时,throw语句中的表达式的值就没有实际意义,而表达式的类型就特别重要
以异常语句如下:
~~~
try
{
可能会出异常的函数或者语句,判断是否出现异常,抛出异常
}
Catch(异常类型 [参数名字] )
{
捕捉异常
}
~~~
其中catch语句可以有多个,以捕捉不同的异常。
(1)异常类型我们可以使用自己定义的,我们写一个程序,除法程序,除数为零,自定义抛出异常类型。
~~~
#include<iostream>
#include<stdexcept>
using namespace std;
class divide_zero //自定义异常类型
{};
int divide(int c, int d) //定义函数,异常类型说明 此处不写,则说明是此函数有可能抛出各种异常
{
if(d==0)
throw divide_zero();//抛出异常类型为 除零异常类型
cout<<"Correct the result is"<<c/d<<endl;
return 0;
}
int main()
{
int a,b;
~~~
~~~
//使用自定义异常类型
cout<<"Please enter the two different integer "<<endl;
cin>>a>>b;
try
{
divide(a,b); //可能发生异常的函数
}
catch(divide_zero) //捕捉到除数为零的异常
{
cout<<"Division by zero"<<endl;
}
return 0;
}
~~~
运行结果是:![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-17_56c446a903d03.jpg)
(2)异常类型可以是我们定义的类,我们也可以定义异常类型是基本数据类型,为此,我们修改上述代码
~~~
#include<iostream>
#include<stdexcept>
using namespace std;
int divide(int x,int y)
{
if(y==0)
throw y;
cout<<"Correct the result is"<<x/y<<endl;
return 0;
}
int main()
{
//自定义类型之使用基本数据类型
try
{
divide(1,0);
}
catch(<span style="color:#ff0000;">int</span>) //<span style="color:#ff0000;">因为除数是整形所以抛出异常时类型是整形才能捕捉(0也是整形)</span>
{
cerr<<"error of dividing zero./n";
exit(1); //异常退出程序
}
}
~~~
运行结果是:![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-17_56c446a912560.jpg)
此时我们也可以用C++的标准异常,为此我们修改上面代码,我们先介绍下C++的标准异常类,使用C++提供给我们的标准异常时,我们需要包含头文件#include<stdexcept>
以下是我查找的C++标准异常类型,引用一个大神![微笑](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-18_569ca449c5105.gif)
的文章
~~~
namespace std
{
//exception派生
class logic_error; //逻辑错误,在程序运行前可以检测出来
//logic_error派生
class domain_error; //违反了前置条件
class invalid_argument; //指出函数的一个无效参数
class length_error; //指出有一个超过类型size_t的最大可表现值长度的对象的企图
class out_of_range; //参数越界
class bad_cast; //在运行时类型识别中有一个无效的dynamic_cast表达式
class bad_typeid; //报告在表达试typeid(*p)中有一个空指针p
//exception派生
class runtime_error; //运行时错误,仅在程序运行中检测到
//runtime_error派生
class range_error; //违反后置条件
class overflow_error; //报告一个算术溢出
class bad_alloc; //存储分配错误
}
~~~
其中的一个重要函数为what(),它返回一个表示异常的字符串指针。
标准库异常类定义在以下四个头文件中
1、exception头文件:定义了最常见的标准异常类,其类名为exception。只通知异常的产生,但不会提供更多的信息
2、stdexcept头文件定义了以下几种常见异常类
函数 功能或作用
exception 最常见的问题
runtime_error 运行时错误:仅在运行时才能检测到的问题
range_error 运行时错误:生成的结果超出了有意义的值域范围
overflow_error 运行时错误:计算上溢
underflow_error 运行时错误:计算下溢
logic_error 逻辑错误:可在运行前检测到的问题
domain_error 逻辑错误:参数的结果值不存在
invalid_argument 逻辑错误:不合适的参数
length_error 逻辑错误:试图生成一个超出该类型最大长度的对象
out_of_range 逻辑错误:使用一个超出有效范围的值
了解完后,我们开始编写程序,在此我们只是简单地使用下标准异常类。
~~~
#include<iostream>
#include<stdexcept>
using namespace std;
int main()
{
//抛出一个小小的异常 使用C++标准异常类
int a,b;
cout<<"Please enter the two different integer "<<endl;
while(cin>>a>>b)
{
try
{
if(b==0)
throw runtime_error("两者不可以相等");
}
catch(runtime_error err)
{
cout<<err.what()<<endl;
}
cout<<"请继续输入"<<endl;
}
return 0;
}
~~~
运行结果是:![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-17_56c446a924234.jpg)
**异常的接口声明**
为了加强程序的可读性,使函数的用户能够方便地知道所使用的函数会抛出哪些异常,可以在函数的声明中列出这个函数可能抛出的所有异常类型,例如:
`void fun() throw( A,B,C,D);`
这表明函数fun()可能并且只可能抛出类型(A,B,C,D)及其子类型的异常。如果在函数的声明中没有包括异常的接口声明,则此函数可以抛出任何类型的异常,例如:
`void fun();`
一个不会抛出任何类型异常的函数可以进行如下形式的声明:
`void fun() thow();`
此时一定要注意:编译器不会对异常说明进行检测,异常说明更多的是写给函数的用户看。
而在一开始函数异常说明的类型与实际抛出的异常类型不匹配的情况中,同样由于“编译器不会对异常说明进行检测”的原因,所以编译通过,异常机制运行正常。比如说,你的声明的类型是整形,结果还是让除数为零的异常接收到了,有人感觉很奇怪,其原因就在于此。![再见](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-25_56a5a366e5bcb.gif)
![再见](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-25_56a5a366e5bcb.gif)
C++ vector介绍
最后更新于:2022-04-01 09:39:06
~~~
<span style="font-family: Arial; ">在此总结一下模板vector的使用介绍</span>
~~~
标准库vector类型使用需要的头文件:#include <vector>。vector 是一个类模板。不是一种数据类型,vector<int>是一种数据类型。它比数组更加方便使用。
## 一、vector定义和初始化
- vector< 类型 > 标识符(最大容量,初始所有值);例如 vector<int> vi(10,2) //有十个元素,初始值都是2
- vector< typeName > vi(n); //vi含有n个值为0的元素 例如 vector<int> vi(10) //有十个元素,初始值都是该容器的默认值,整形是0
- vector<typeName>v2(v1); 或v2=v1;或vector<typeName> v2(v1.begin(), v1.end());//v2是v1的一个副本
- vector< typeName > v1; //默认v1为空,
- int a[4]={0,1,2,3}; vector<int> v5(a,a+5);//v5的size为5,v5被初始化为a的5个值。后一个指针要指向将被拷贝的末元素的下一位置。
- 还有一种较为特殊,列表初始化(使用中括号来初始化) vector<int> v{10}; //模板只有一个元素,初始值为10,此种方法一定要和值初始化(使用圆括号初始化分别开),此定义模板方法在只有C++11的规则下才可以
## 二、vector中压入元素,输出元素操作
我们可以在程序运行时压入元素,此时我们要使用 push_back()语句
~~~
- vector<string> str;
string text;
while(cin>>text)
{
str.push_back(text);
} //各元素之间以空白字符为间隔(制表符,空格,回车 都是空白字符)
- 压入元素后我们就要输出它,此时我们可以利用C++迭代器来一次遍历的输出它
(1)vector<int> vi;
for(int i=0;i<100;i++)
vi.push_back(i); //压入一百个与元素
auto i=vi.size(); //size(),返回模板中元素的个数 (下面三.3有解释)
cout<<i<<endl;
for(auto b=vi.begin();b<vi.end();b++) //使用C++迭代器
cout<<*b<<endl;
(2)我们还可以使用下标来输出,例如
for(int i=0;i<vi.size();i++) //使用下标输出
cout<<vi[i]<<endl;
~~~
## 三 对vector操作的一些函数
1. v[n] 返回v中位置为n的元素
1. v.empty() 判断vector是否为空
1. v.size() 返回容器中数据的个数,size返回相应vector类定义的size_type的值。我们可以使用auto类型,从而不必担心我们定义的类型(上面程序有演示)
1. v.pop_back() 删除容器的末元素,并不返回该元素。
1. v.push_back(t) 在容器的最后添加一个值为t的数据,容器的size变大。
1. v.erase(pointer1,pointer2) 删除pointer1到pointer2中间(包括pointer1所指)的元素。
1. v.resize(2*v.size, 10) 将v的容量翻2倍(并把新元素的值初始化为10)
1. v1==v2 判断v1与v2是否相等。!=、<、<=、>、>= 保持这些操作符惯有含义(使用的逻辑和普通的一样,我们可以对比C++string)
1. v.clear() 删除容器中的所有元素。
下面根据以上的函数,编写一个程序当用例,使用起来更加清晰
~~~
#include<iostream>
#include<string>
#include<vector>
#include<cctype>
using namespace std;
int main()
{
vector<int> vi;
for(int i=0;i<20;i++)
vi.push_back(i); //压入元素。向模板的底端
decltype(vi.size()) size=vi.size();
//使用decltype 定义一个与vi.size()类型一样的变量 ,模板中对象的个数
cout<<"the vector's capacity is"<<size<<endl;
vi.pop_back(); //删除容器中最后一个元素 19
for(auto i=vi.begin();i!=vi.end();i++) //输出模板中的元素
cout<<*i<<endl;
for(int i=0;i<19;i++) //使用下标输出
cout<<vi[i]<<endl;
vi.resize(vi.size()*2,12);//长度变成两倍,并主动为后增加的赋值为12
cout<<"NOW the vector's capacity is"<<vi.size()<<endl;
for(auto i=vi.begin();i!=vi.end();i++)
cout<<*i<<endl;
vi.clear(); // 删除容器中的所有元素
cout<<"After clear , the vector's capacity is"<<vi.size()<<endl;
//使用C++迭代器类型遍历每一个元素
vector<string> s(10,"hello");
vector<string>::iterator i;
for(i=s.begin();i!=s.end();i++)
cout<<*i<<endl;
s.pop_back(); //删除容器中最后一个元素
cout<<"After pop_back , the vector's capacity is"<<s.size()<<endl;
return 0;
}
~~~
以上部分结果截图如下
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-17_56c446a8b81b7.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-17_56c446a8c841f.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-17_56c446a8d4f07.jpg)
## 四 关于vector的迭代器类型
1. 每种容器类型都定义了自己的迭代器类型,如vector:
vector<int>::iterator iter;这条语句定义了一个名为iter的变量,它的数据类型是由vector<int>定义的iterator类型。
1. 使用迭代器读取vector中的每一个元素:
~~~
vector<int> ivec(10,1);
for(vector<int>::iterator iter=ivec.begin();iter!=ivec.end();++iter)
{
cout<<*iter<<endl;; //使用 * 访问迭代器所指向的元素
}
~~~
我们可以定义vector<int>::iterator,同时我么也可以定以如下vector<int>:: const_iterator ,只能读取容器中的元素,而不能修改。
iterator除了进行++,--操作,可以将iter+n,iter-n赋给一个新的iteraor对象。还可以使用一个iterator减去另外一个iterator.
const vector<int>::iterator newiter=ivec.begin();
vector<int>::iterator newiter2=ivec.end();
cout<<"\n"<<newiter2-newiter;
一个完整的程序
~~~
1 #include <vector>
2 #include <iostream>
3
4 using namespace std;
5
6 int main() {
7 vector<int> ivec;
8 ivec.push_back(1);
9 ivec.push_back(2);
10 ivec.push_back(3);
11 ivec.push_back(4);
12
13 for(vector<int>::iterator iter = ivec.begin();1. iter != ivec.end(); ++iter)
14 cout << *iter << endl;
15 }
~~~
以上是vector的一些使用的知识点,希望对大家有用![微笑](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-18_569ca449c5105.gif)
C++string的使用
最后更新于:2022-04-01 09:39:04
## 在这里总结一下string的用法
String是可变长字符串,使用的时候要包含string头文件。
要想使用标准C++中string类,必须要包含
~~~
#include <string>// 注意是<string>,不是<string.h>,带.h的是C语言中的头文件
using std::string;
using std::wstring;
或
using namespace std;
下面你就可以使用string/wstring了,它们两分别对应着char和wchar_t。
~~~
初始化:
~~~
string s1; //是一个空的string
strings2(s1);
strings3="hello"; //拷贝初始化
strings4=s3; // 将s3的内容拷贝给s4
strings5(10,'c'); //直接初始化,string中有十个c
strings6("hello"); //s6中内容是hello
~~~
**对string读写操作:**
~~~
(2) string a; //读写string对象
while(cin>>a)
{
cout<<a<<endl;
}
while(getline(cin,a))
cout<<a<<endl;
cout<<a<<endl;对于此类的操作,string对象对此操作也是返回运算符左侧的运算对象作为其结果。即,可以实现级联操作。
相当于, cout<<s1<<s2<<s3<<endl;
cin<<s1<<s2<<s3<<endl;
(2)当我们遇到不知道具体数量的string时
stringword;
while(cin>>word)
cout<<word<<endl;
~~~
只有当cin插入流遇到文件结束或者非法输入时才结束。(例如空白字符,或者文件结束标志)
(3)**关于string的对象的操作:**
string s;
~~~
s.empty(); //当string对象为空时,返回真,否则返回假
s.size(); //返回S中字符的个数(但返回的类型并不是int 类型,而是size_t类型,一种无符号的类型)
~~~
使用两个等号的比较运算符时‘==’,当且仅当两个string对象中字符一对一相等并且长度相等。
String对象可以直接相加
~~~
string S1=”hello”;
string S2=” C++”;
String s3=s1+s2; //S3的内容是 “hello C++”
~~~
String 对象也能可字面值(字符串)进行相加
~~~
String s1=”hello”;
S1=s1+” C++” //s1 的结果仍然是hello C++
~~~
(4)**关于string的一些操作函数**
Isspace(c) 当c是空白字符时(空格回车 制表),返回真
tolower(c) 大写转小写
toupper(c) 小写转大写
对于处理string中的字符
我们有如下方法,我们以大小写转换为例
~~~
stringstr3="a,b,c";
decltype(str3.size())i; 我们使用decltype()来获取string中字符下标的类型
for(i=0;i<str3.size();i++)
str3[i]=toupper(str3[i]);
cout<<str3<<endl; string的 内容变为大写 A B C
~~~
我们还可以通过使用迭代器来遍历字符,我们使用auto关键字,auto会自动根据初始值的类型来设定我们定义的变量
~~~
//stringstr("zheng");
//for(autoi=str.begin();i!=str.end();i++) i的类型就是size_t类型
// *i=toupper(*i);
// cout<<str<<endl; 输出的结果是大写 ZHANG
~~~
**string的特性描述:**
~~~
intcapacity()const; //返回当前容量(即string中不必增加内存即可存放的元素个数)
intmax_size()const; //返回string对象中可存放的最大字符串的长度
intsize()const; //返回当前字符串的大小
intlength()const; //返回当前字符串的长度
boolempty()const; //当前字符串是否为空
void resize(intlen,char c);//把字符串当前大小置为len,并用字符c填充不足的部
~~~
最后要介绍如何在Win32 应用程序中引用MFC中的部分类,例如CString。
1.在工程目录下右键选择"Properties”--->"Configuration Properties”--->“General”--->"Use of MFC"--->"Use MFC in a Static Library",
默认的是:"Use Standard Windows Libraries",
c++学习笔记之变量
最后更新于:2022-04-01 09:39:02
变量的命名规则:标示符要能体现含义,变量的名字一般用小写,用户自己定义的类一般第一个字母大写,如果标示符有多个单词组成,则需要加下划线。
变量声明和定义的关系:程序有多个文件组成,有时候需要再多个文件中共享一个变量,此时我们需要分离式编译,一定注意:变量的定义只有一次,而变量的声明却可以是多次的,在一个文件中需要用到变量时,我们就需要声明。
其中,如果是想声明一个变量而不是去定义它(我们平时定义变量时,也是声明它,但声明变量和定义有着本质的区别),则我们使用如下语句:
在变量前加上extern关键字即可,记住:任何包含显示初始化的声明即成为定义,我们可以给由关键字标记的变量附上一个初始值,此时的声明就变成了定义。
## 变量的作用域:
C++中作用域是程序中的一部分一段,C++中大多数域是以花括号分隔的。同一个名字在不同的域中是不同的,名字的有效区就是在他所在的花括号里。具体程序如下图。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-17_56c4469dd4cd1.jpg)**
## 引用:
我们可以把引用看做是为变量起了另外一个名字,引用需要加&声明符。例如
~~~
int a=10;
int &A=a; //A是a的另一个名字
A=20; //把20赋给A指向的对象,也就是a
~~~
谨记:引用必须要初始化,定义引用时,程序把它的初始值绑定,并不是将初始值复制给引用,这一概念要搞清楚,一点初始化完成,就不可以让引用重新绑定。所以需要初始化。我们操纵一个引用,就是在操纵引用所绑定的变量。对引用的赋值就是对与之绑定的变量的赋值。
## 指针:
指针本身就是一个对象,我们可以对指针赋值拷贝,指针在生命期中,可以指向不同的对象,并且指针无需在定义指针的时候初始化,指针可以不初始化,此时它有一个不确定的值。
空指针:不指向任何对象,让一个指针为空有三种形式,如下。
~~~
int *p=NULL;
int *q=nullptr; //使用字面值nullptr来初始化
int *ptr=0;
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-17_56c446a8a50f3.jpg)
建议初始化所 有指针,若指针没有初始化,当指针指向的变量不存在时,指针还仍然指向其变量所在的内存地址,此时若程序中引用此指针,会造成“虚悬指针”,只是很危险的,所以当我们不知道指针应该指向何处时,我们应该让他指向空,这样更加保险。要知道,调试指针错误很让人头疼。
前言
最后更新于:2022-04-01 09:39:00
> 原文出处:[C++真知灼见](http://blog.csdn.net/column/details/dhls.html)
作者:[u014028070](http://blog.csdn.net/u014028070)
**本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!**
# C++真知灼见
> 总结一些C++中的容易混淆的概念和知识,常用的编写技能,以及对C++深层次的理解与看法。