1,必不可少的“hello world”
#include<iostream>int main(int argc, char** argv) {std::cout << "hello world" << std::endl;return 0; }
这个是一个极其简单的程序,虽然没有多大简直,但是可以体现c++程序格式方面的一些重要概念
2,预编译指令
要构建一个c++程序,这个过程可以分为四个步骤,预处理,编译,汇编,链接,走完着四个过程可以构成一个应用,这四个如果有c语言基础应该都知道,如果没有那就去看我的文章程序的环境和预处理
# 表示交于预处理器处理的指令,以这个打头
include 表示指出预处理要取得这个文件中的所有的内容,并使之对当前文件可用
在C中,所包含的文件通常以.h结尾,如<stdio.h>。在C++中,标准库头文件的后缀可以忽略不要,如<iostream>。C中常用的一些标准头文件在C++中仍然存在,不过文件名可能有所不同。例如,通过包含<cstdio>,可以访问<stdio.h>中的功能
常见的预处理指令
第一个和第二个真的是再常见不过了学习c语言的时候
第三个就是预编译指令,相当于我们学习的if,else if,else这三个
最后一个#pragma在C++中一般是用来调整内存对齐,设置编译器的警告级别或者启用一些编译器特有的优化选线那个,比如你自己编写的头文件只可以插入一次就写,#pragma once 这样的指令
3,main函数
顾名思义就是整个函数的入口
4,I/O流
如果你刚开始接触C++,并且有C的背景,很可能不清楚std::cout是什么,不明白为什么这里不用原先值得信赖的 printf()。尽管在C++用 printf()也是可以的,但是C++流库提供的输人/输出工其相
比之下要好得多不过输出的基本思想相当简单。可以把输出流看作是数据的一个清洗槽。你扔到这个“槽”里的所有东西都会得到适当的输出。std::cout就是对应用户控制台(即标准输出,standard out)的“槽”。除此之外,还有其他的一些槽,如 std::cert、它会输出至错误控制台。“<<”操作符就是把数据扔到槽里。在上述例子中,则是把一个加引号的文本串发送至标准输出。基于输出流,允许在一行代码上向流中顺序地发送多种不同类型的数据。以下代码就会输出一个文本,其后是一个数字,再后面又有另一个文本。
std::cout<<"There are"<< 219<<"ways I love you."<< std::endl;
std::endl表示-个行尾字符。输出流遇到std::endl时,它会将到目前为止发送到槽中的所有内容统统输出,并移至下一行。还有…种方法可表示行尾,即使用'\n’字符。\n字符是一个转义字符(escape character),它表示一个换行字符。转义字符可以用在任何加引号的文本串中。常见的转义字符
1 \n换行
2 \r回车
3 \t跳格
4 \\反斜线字符
5 ”引号
流还可以接受一个用户的输入,为此最简单的方法就是对输入流使用“>>”操作符,std::cin输入流可以接受用户的键盘输入,用户输入可能很复杂,因为你并不知道用户会输入什么样的数据。
5,命名空间
命名空间主要是解决不同代码部分之间的命名冲突问题
例如:你在编写代码的时候,代码里面包含一个命名为foo()的函数,后来有一天,你需要使用一个第三方库,这个第三方库里面也有一个foo()函数,编译器看到foo()函数的时候,无论知道你指的是哪一个版本。一方面你无法修改第三方库的函数名,另一方面你自己的函数名也是困难重重,这个时候就需要命名空间来救援了
#include <iostream> using namespace std;namespace LOVE {void print() {cout << "I LOVE YOU" << endl;} }using namespace LOVE; int main() {print();return 0; }
这里我们调用命名空间的时候,我们可以使用LOVE::print也可以运用using namespace LOVE来声明以下,这样就可以不需要写这个LOVE::这个作用域了,直接使用print函数
6,using的用法
1,声明某个命名空间 using namespace std;
2,声明某个特定项 using std::cout;
一个源文件可以包含多个using指令,尽管这是一条捷径,但要注意不要滥用
在极端的情况下,如果通过using指令声明要使用已知的所有命名空间,实际上就会使命名空间根本无法起作用!如果用using指令使用了两个命名空间,而这两个命名空间中包含有相同的名字,那么还会导致命名冲突
需要知道你的代码是在哪个命名空间中操作,这一点也很重要,如此就能避免偶尔犯错,不至于调用了不正确的同名函数
7,变量
未初始化为题的细节讨论
在C++中,可以在代码中的任何位置声明变量(variable),而且一旦声明,当前块中在声明行之后的任何位置都可以使用该变量。实际上,工程小组应当确定是在每个函数的开始处声明变量,还是根据需要在不定的位置上声明。声明变量时,可以不为变量提供值。这种未赋值的变量往往会有一个“半随机”的值,这取决于当前内存中相应位置上恰好是什么值,而这往往会带来大量的bug。C++中,变量在声明时可以为之赋一个初始值。
这个半随机值也称为垃圾值
未初始化的值往往是显示该位置的内容,下面举两个例子
1 可能是其他变量声明周期结束之后遗留再那个地方的值
2 初始化栈的时候,初始化的ccc字母显示的是这个,如果这个不明白可以去看我的函数栈帧
自主思考的问题:为什么这个编译器不把这个栈上的值给重置,而是遗留再那里呢?
1,性能的优化:提高效率,清空内存是需要额外的开销的
2,内容的管理:栈内里面快速分配的,所以不回收,一下就会被其他的变量所占据,为了提高性能就没有初始化了
3,程序员的责任:为了提高程序员对待变量初始化的重要性,所以就会有了这个,避免使用未初始化的变量
8,短路逻辑
C++在计算表达式时使用一种“短路逻辑”(short-circuit logic)。这说明,一旦能够确定最后结果,表达式中的余下部分就不必再计算了。例如,在做以下多个布尔表达式的逻辑或操作时,只要发现其中一个布尔表达式为true,就可以确定结果肯定为true。那么余下的布尔表达式就根本不需要检查了
短路逻辑问题代码1:
#include <iostream>bool foo() {std::cout << "foo() is called" << std::endl;return true; }int main() {bool x = false;if (x && foo()) { // foo() 不会被调用,因为 x 为 false,短路std::cout << "Inside if block" << std::endl;}else {std::cout << "Inside else block" << std::endl;}return 0; }
这里可以看出foo()函数并没有执行,所以可以看出这个短路逻辑,这里直接执行了这个else的语句
9,C++风格的字符串
在C语言中有这样表示字符串的
char arraystring[20] = "hello world"; char* pointerstring = "hello world";
第一个是用数组装这个字符串
第二个是把字符串存储到只读数据段,然后用这个指针指向那个字符串
放到栈上和堆上的各个优点:(对象)
放到栈上,就是生命周期短,但是很方便清理,编译器会自动清除
放到堆上,就是生命周期长,但是不方便清理,需要自己手动清除
C++风格字符串
在C++中可以不需要用strcat()连接两个字符串,可以用+来连接字符串,然后可以用==来比较着两个字符串,对比两个内容,但是C语言中是比较他们的地址
10,引用
大多数函数都有一个共同的模式,它们会取0个或多个参数,完成某些计算,并返回一个结果。不过,有时也可能打破这种模式。你可能想要返回两个值,或者希望函数能够改变传人的某个参数的值。在C中,要达到这样一些目的,主要的做法就是传入变量的指针,而不是传人变量自身。这种方法惟一的缺陷在于,对于原本很简单的任务,可能会因此引入繁杂的指针语法。
在C++中,对“按引用传递”(即传引用)有一个明确的机制。如果对一个类型附加了&,这就表示该变量是一个引用。仍然会把它当常规的变量来使用,不过就其本质而言,它实际上是原变量的一个指针。以下是一个addOne()函数的两种实现。
void addone(int i){ i++;//Has no real effect because this is a copy of the original}
void addone(int& i){ i++;//ctually changes the original variable}
这个引用就类似于指针了,不会创建新的副本,而是对于原有的进行改动,但是这个是常量指针,所以有更好的安全性,不可以改变方向,但是可以改变值
11,异常
异常是一个类,是指在程序运行时,过程中发生的一些异常的事件,比如:数组下标溢出,除0溢出,所要读的文件不存在
C语言中的异常是通过返回值来决定异常的,返回值为0就是成功,返回值为1就是异常,但是这个利用率很低,因为函数的返回值是可以忽略的,然而在C++里面,异常是不可以被忽略的,因为忽略异常代表程序结束
整形的返回值是没有任何语义信息的,但是异常的返回值是由信息的,因为可以从你的类名就可以体现出来
异常的语法:
1,try{ }这个是用来装载一切异常的抛出的
2,throw()这个是用来抛出异常值 catch()这个是用来接受异常值
catch(类型名 异常值)只有当异常值和类型名都是一致的才可以进行捕捉
12,面向对象的程序(员工程序)
员工的头文件
#pragma once #include<iostream> using namespace std; const int kDefaultStartingSalary = 10000; namespace Records {class Employee {public:Employee();void promote(int inRaiseAmount = 1000); //给员工加薪水void demote(int inDemeritAmount = 1000); //给员工降薪水void hire(); //雇佣或者再次雇佣人员void fire(); //解雇人员void display(); //输出人员的信息到控制台//访问和设置器(accessors 和 setters)void setFirstName(string inFirstName); //设置员工的名字string getFirstName(); //获得员工的名字void setLastName(string inLastName); //设置员工的姓氏string getLastName(); //获得员工的姓氏void setEmployeeNumber(int inEmployeeNumber); //设置员工的编号int getEmployeeNumber(); //获得员工的编号void setSalary(int inNewSalary); //设置员工的薪水int getSalary(); //获得员工的薪水bool getIsHired(); //检查员工是否被解雇private:string mFirstName;string mLastName;int mEmployeeNumber;int mSalary;bool fHired;}; }
介绍
这个是对于员工的薪水,名字,编号,是否解雇的设置与获取
员工的增加与下降薪水,雇佣和解雇人员,输出信息到控制台
注意点
1,命名空间头文件的声明
这里因为我们后面要把这个类放到这个命名空间里面,为了放置于其他的函数名字冲突,我们在头文件的时候就需要写下这个namespace才可以,要不然后面我们在源程序把这些函数写道这个命名空间里面不行,因为会找不到
2,常量
一开始的常量是员工的初始工资
员工的源程序
#include"Employee.h" namespace Records {Employee::Employee() {mFirstName = " ";mLastName = " ";mEmployeeNumber = -1;mSalary = kDefaultStartingSalary;fHired = false;}//加薪水void Employee::promote(int inRaiseAmount) {setSalary(getSalary() + inRaiseAmount);}//降薪水void Employee::demote(int inDemeritAmount) {setSalary(getSalary() - inDemeritAmount);}//雇佣或者再次雇佣人员void Employee::hire() {fHired = true;}//解雇人员void Employee::fire() {fHired = false;}//控制台void Employee::display() {cout << "Employee: " << getLastName() << ", " << getFirstName() << endl;cout << "-------------------------------" << endl;cout << (fHired ? "Current Employee" : "Former Employee") << endl;cout << "Salary: $" << getSalary() << endl;cout << endl;}//访问和设置器(accessors 和 setters)//设置员工的名字void Employee::setFirstName(string inFireName) {mFirstName = inFireName;}//获得员工的名字string Employee::getFirstName() {return mFirstName;}//设置员工的姓氏void Employee::setLastName(string inLastName) {mLastName = inLastName;}//获得员工的姓氏string Employee::getLastName() {return mLastName;}//设置员工的编号void Employee::setEmployeeNumber(int inEmployeeNumber) {mEmployeeNumber = inEmployeeNumber;}//获得员工的编号int Employee::getEmployeeNumber() {return mEmployeeNumber;}//设置员工的薪水void Employee::setSalary(int inNewSalary) {mSalary = inNewSalary;}//获得员工的薪水int Employee::getSalary() {return mSalary;}//检查员工是否被解雇bool Employee::getIsHired() {return fHired;} }
介绍
我们这里写了这个员工的构造函数和析构函数,由于我前面没有写using namespace Employee这个指令,所以后面的函数都是需要写Employee作用域的,这里由头文件里面声明的函数的功能
函数解释
1 升降薪水
这里就是传入你所想要的升降薪水的金额,然后执行
2 雇佣和解雇人员
这个就是要依靠那个开关了,开关是打开则证明还在职,关闭则是不在了
3 名字与编号
这个就是名字的输入与使用和编号的输入与使用
4 控制台
就是打印相关的信息到控制台上面
数据库的头文件
#pragma once#include"Employee.h"namespace Records {const int kMaxEmployees = 100; //最大员工数量 const int kFirstEmployeeNumber = 1000; //数据库需要负责自动地为新员工分配一个员工号,因此这里需要定义一个常量class Database {public:Database();~Database();//这个就是引用,把传入地对象进行放置后,// 返回地时候是返回那个那个位置地对象,而不是重新开辟一个Employee& addEmployee(string inFirstName, string inLasName);//这个函数的作用是将新员工添加到数据库中,并返回该员工对象Employee& getEmployee(int inEmployeeNumber);//根据员工编号 inEmployeeNumber 获取并返回对应的员工对象的引用Employee& getEmployee(string inFirstName, string inLastName);//根据员工的名字(inFirstName 和 inLastName)查找并返回对应员工对象的引用void displayAll();//显示所有员工的详细信息void displayCurrent();//显示所有现在的员工的详细信息void displayFormer();//显示所有已经解雇的员工的详细信息。protected:Employee mEmployees[kMaxEmployees];//用于存储所有员工的 Employee 对象int mNextSlot;//表示下一个可用的员工槽(在 mEmployees 数组中的索引)。它跟踪下一个空的数组位置int mNextEmployeeNumber;//为每个新添加的员工分配的下一个员工编号。}; }
介绍
这里写了构造函数和析构函数用于初始化和清除
还有一些功能函数
增加员工:需要输入他的名字
得到员工:有两种:1 员工的编号 2 员工的名字
展示信息:有三种:1 展示全部的员工 2 展示在职的员工 3 展示离职的员工
变量
1 员工数组(类似于结构体数组,自定义数组)
2 下一个员工槽
3 员工编号(这里的员工编号我们初始化为1000,然后后面加数字就好了)
注意
这里的函数的返回值选择的是引用
因为我们选择引用就类似于指针一般,这样就可以不需要创建额外的副本来整这个变量,这样就可以减少开销了
数据库的源文件
#include"Database.h" #include"Employee.h" namespace Records {Database::Database() {mNextSlot = 0;mNextEmployeeNumber = kFirstEmployeeNumber;}Database::~Database() {}//按照名字添加员工Employee& Database::addEmployee(string inFirstName, string inLastName) {if (mNextSlot >= kMaxEmployees) {cerr << "data structure is no more room" << endl;throw exception();//std::exception 是 C++ 标准库中所有异常类型的基类// 它提供了一个通用的异常接口(主要是 what() 方法),但是它没有具体的错误信息或者类型// 通常情况下,我们不会直接抛出 std::exception,而是抛出其 派生类,这些派生类能够提供更具体的错误信息}Employee& theEmployee = mEmployees[mNextSlot++];theEmployee.setFirstName(inFirstName);theEmployee.setLastName(inLastName);theEmployee.setEmployeeNumber(mNextEmployeeNumber++);theEmployee.hire();return theEmployee;}//通过工号来寻找员工Employee& Database::getEmployee(int inEmployeeNumber) {for (int i = 0;i < mNextSlot;i++) {if (mEmployees[i].getEmployeeNumber() == inEmployeeNumber) {return mEmployees[i];}}cerr << "No employee with employee number " << inEmployeeNumber << endl;throw exception();}//按照员工地名字来寻找员工Employee& Database::getEmployee(string inFirstName, string inLastName) {for (int i = 0;i < mNextSlot;i++) {if (mEmployees[i].getFirstName() == inFirstName&& mEmployees[i].getLastName() == inLastName) {return mEmployees[i];}}cerr << "No match with name" << inFirstName << " " << inLastName << endl;throw exception();}//显示所有地员工void Database::displayAll() {for (int i = 0;i < mNextSlot;i++) {mEmployees[i].display();}}//显示现在所有地void Database::displayCurrent() {for (int i = 0;i < mNextSlot;i++) {if (mEmployees[i].getIsHired()) {mEmployees[i].display();}}}//显示之前地void Database::displayFormer() {for (int i = 0;i < mNextSlot;i++) {if (!mEmployees[i].getIsHired()) {mEmployees[i].display();}}} }
函数的介绍:
构造函数初始化,析构函数为空
功能函数
分别为,添加员工,获得员工信息,显示自己想要条件的全部员工
显示自己想要条件的员工
利用解雇的开关的开启和关闭来设置条件就好了,全部都不需要这个条件
添加员工
就是利用mNextSlot来索引数组就好了
寻找员工
利用输入的数据和数组里面的数据进行对比寻找
用户界面源文件
#include<stdexcept> #include<iostream> #include "Database.h" #include"Employee.h"using namespace std; using namespace Records;int displaymeau(); void doHire(Database& inDB); void doFire(Database& inDB); void doPromote(Database& inDB);int main() {Database employeeDB;bool done = false;while (!done) {int selection = displaymeau();switch (selection) {case 1:doHire(employeeDB);break;case 2:doFire(employeeDB);break;case 3:doPromote(employeeDB);break;case 4:employeeDB.displayAll();break;case 5:employeeDB.displayCurrent();break;case 6:employeeDB.displayFormer();break;case 0:done = true;break;default:cerr << "Unkonwn commend" << endl;}} }int displaymeau() {int selection;cout << endl;cout << "Employee Database" << endl;cout << "-------------------------" << endl;cout << "1)Hire a new employee" << endl;cout << "2)Fire an employee" << endl;cout << "3)Promote an employee" << endl;cout << "4)List all employees" << endl;cout << "5)List all current employees" << endl;cout << "6)List all previous employees" << endl;cout << "0)Quit" << endl;cout << endl;cout << "please input---------->" << endl;cin >> selection;return selection; }void doHire(Database& inDB) {string firstName;string lastName;cout << "please input Firstname" << endl;cout << "------->";cin >> firstName;cout << "please input Lastname" << endl;cout << "------->";cin >> lastName;/*try 块:包围了可能抛出异常的代码。catch 块:捕获并处理异常。可以有多个 catch 块来捕获不同类型的异常。catch (...):表示捕获所有类型的异常。*//*try 块内的代码会被执行。如果在执行过程中发生异常,程序会跳到 catch 块进行处理。*//*exception 是 C++ 标准库中的一个基类类型,位于 <exception> 头文件中。它是所有异常类的基类,任何抛出的异常都可以继承自 exception 类expection()这个是构造函数,用于初始化异常的*/try {inDB.addEmployee(firstName, lastName);}catch (exception ex) {cerr << "Unable to add new employee" << endl;} }void doFire(Database& inDB) {int employeeNumber;cout << "please input Employee number";cout << "------>";cin >> employeeNumber;try {Employee& emp = inDB.getEmployee(employeeNumber);emp.fire();//指程序已经被系统终止或异常中止cout << "Employee" << employeeNumber << "has been terminated" << endl;}catch (exception ex) {cerr << "Unable to terminate employee" << endl;}}void doPromote(Database& inDB) {int employeeNumber;int raiseAmount;cout << "Employee number" << endl;cout << "------>";cin >> employeeNumber;cout << "How much of a raise";cout << "------>";cin >> raiseAmount;try {Employee& emp = inDB.getEmployee(employeeNumber);emp.promote(raiseAmount);}catch (exception ex) {cerr << "Unable to promote employee" << endl;} }
整体逻辑就是,员工的功能函数为一个cpp(加载站)————>数据库为一个cpp里面为整理这个员工的数据到数组里面去(整理站)————>最后利用函数把数据展现出来(展览站)
我们创建了一个数据库对象
这里的功能函数传递的参数都是引用类型,这个相当于指针,直接对首次设置的变量进行处理,可以减少副本,减少开销
void doHire(Database& inDB);
void doFire(Database& inDB);
void doPromote(Database& inDB);这个就是传入这个引用
13,补充
异常:
try 块:包围了可能抛出异常的代码
catch 块:捕获并处理异常。可以有多个 catch 块来捕获不同类型的异常
catch (...):表示捕获所有类型的异常try 块内的代码会被执行
如果在执行过程中发生异常,程序会跳到 catch 块进行处理
exception 是 C++ 标准库中的一个基类类型,位于 <exception> 头文件中,所以可以弄这个变量
它是所有异常类的基类,任何抛出的异常都可以继承自 exception 类
expection()这个是构造函数,用于初始化异常的以上面的代码为例子:
try {inDB.addEmployee(firstName, lastName); } catch (exception ex) {cerr << "Unable to add new employee" << endl; }
这个就是try里面看有没有抛出异常,catch里面虽然是一个ex,但是只要这个ex有值就会出现异常,所以决定权是看有没有抛出异常
注:这里不是抛出准确的异常就发生异常,而是抛出异常之后就发生异常
我们来看addEmployee里面的函数里面的代码
Employee& Database::addEmployee(string inFirstName, string inLastName) {if (mNextSlot >= kMaxEmployees) {cerr << "data structure is no more room" << endl;throw exception();//std::exception 是 C++ 标准库中所有异常类型的基类// 它提供了一个通用的异常接口(主要是 what() 方法),但是它没有具体的错误信息或者类型// 通常情况下,我们不会直接抛出 std::exception,而是抛出其 派生类,这些派生类能够提供更具体的错误信息}Employee& theEmployee = mEmployees[mNextSlot++];theEmployee.setFirstName(inFirstName);theEmployee.setLastName(inLastName);theEmployee.setEmployeeNumber(mNextEmployeeNumber++);theEmployee.hire();return theEmployee; }
这里哟写出,如果有溢出的话就会抛出异常,这个exception是一个构造函数,这个构造函数就是初始化用的,然后让外面的catch去捕抓, exception不知道是什么异常,反正就是抛出一个异常,catch就抓住异常打印异常
这个通常自己写异常很有用
对象利用栈和堆的不同点
堆一般是自己手动释放,栈一般是编译器释放,这个可以取决一个对象的声明周期
前置声明
前置声明是解决依赖和提高效率的
依赖:需要放到对应位置
提高效率:提前告诉编译器有这个函数的存在
错误:如果有上述函数声明,但是没有与之对应的函数的话,就会报链接错误,因为字符表找不到
好习惯:函数声明一般放到头文件,函数的源代码是放到源文件
枚举优点
枚举是为了防止你跟别人合作的时候,无缘无故就把对应改了,枚举是利用数字去代替物体,这样就很具有可读性,但是除了这个优点就是防止后面有人改了这个对应关系,就比如你命令大王就是1,但是万一后面减了1为0或者别人定义大王为-1,这样项目合集的时候就会很多报错,但是枚举就可以很好的解决这个问题,它很严格为这些变量赋予了一个值域,注意枚举是从0开始赋值,如果默认的话