目录
- 内联函数
- 简介
- 与带参数宏的区别
- 引用
- 创建引用
- C++ 引用 vs 指针
- 把引用做参数
- 把引用做返回值
- 构造函数、拷贝构造函数、赋值运算符重载函数
- 从一个程序题入手
- 构造函数
- 拷贝构造函数
- 赋值运算符重载函数
- 拷贝构造函数和赋值运算符重载函数的区别
- 程序分析
- 命名空间
- 作用
- 创建及使用方法
- 定义命名空间
- 调用命名空间的函数或变量
- 实例
- c++预定义宏
- 异常处理
- 抛出异常
- 捕获异常
- 标准异常
- 文件IO,文件流
- 文件流的三个数据类型
- 文件流操作
- open()函数
- 写入文件:
- 读取文件
- 关闭文件
- 文件读写实例
- 动态内存
- c++的程序中的堆栈
- new与delete
- 模板、泛型编程
- 模板的概念
- 函数模板
- 类模板
内联函数
简介
在函数前面用inline关键字修饰的函数叫内联函数。
inline ret-type func(parameter list);
内联函数又叫编译时期展开函数,与带参数宏的实现方式类似,在编译时进行上下文替换,保证程序执行效率,但是消耗比单纯定义函数更多的占用空间。
与带参数宏的区别
- 宏的调用不执行类型检查,内联函数像普通函数一样要进行类型检查;
- 宏难以表达的内容用内联函数;
- 内联函数输出的调试信息比宏更完善。
引用
创建引用
引用是一个变量或对象的别名,对引用的操作和对其对象的操作等价
语法:类型 &引用名=目标变量名;
注意:
- &不是取地址运算符,只是标志作用;
- 引用类型和其对象类型必须相同
- 引用必须初始化
#include<iostream>
using namespace std;
int main()
{
int a;
int &b = a;//int &b 不正确,必须初始化;引用类型必须和对象一样
a = 6;
cout << "a="<<a<<" b="<<b<<endl;
b = 9;
cout << "a="<<a<<" b="<<b<<endl;
return 0;
}
C++ 引用 vs 指针
引用和指针很容易混淆,主要有以下不同:
- 不存在空引用,引用必须连接到一块合法内存;
- 引用初始化后,只能指向初始化的对象,不能像指针一样在需要的时候更改指向的对象;
- 引用创建时必须初始化,指针可以在任何时候初始化。
把引用做参数
与指针类似,直接用引用操作内存,调用函数返回后对变量的操作依然有效
实例:
#include<iostream>
using namespace std;
void swap(int &a,int &b)
{
int temp;
temp = a;
a = b;
b = temp;
}
int main()
{
int a = 3,b = 6;
int &c = a,&d = b;
cout << "交换前:a = "<<a<<" b="<<b<<endl;
swap(c,d);
cout << "交换前:a = "<<a<<" b="<<b<<endl;
return 0;
}
把引用做返回值
当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边。
#include<iostream>
using namespace std;
int value[] = {1,2,3,4,5};
int &setVal(int a)
{
return value[a];
}
int main()
{
for(int i=0;i<5;i++)
{
cout<<"改变前:value["<<i<<"] = "<<value[i]<<endl;
}
setVal(2) = 6;//调用返回引用的函数
setVal(4) = 7;
cout <<endl;
for(int i=0;i<5;i++)
{
cout<<"改变后:value["<<i<<"] = "<<value[i]<<endl;
}
return 0;
}
注意:
返回超出函数作用域的引用是不合法的,但是可以返回一个用static修饰的静态变量的引用
int &func()
{
int a;
//return a;//编译时报错
static int b;
return b;//在函数作用域外仍然有效
}
构造函数、拷贝构造函数、赋值运算符重载函数
从一个程序题入手
#include<iostream>
using namespace std;
class A{
public:
A()
{
cout<<1;
}
A(A &a)
{
cout<<2;
}
A &operator=(const A &a)
{
cout<<3;
return *this;
}
};
int main()
{
A a;
A b = a;
A c;
c = a;
cout<<endl;
return 0;
}
构造函数
构造函数在创建类的对象时才调用。
拷贝构造函数
如果一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数是拷贝构造函数。
拷贝构造函数的应用场景:
- 用一个对象初始化另外一个对象。(eg:
NumberArray second = first;
) - 函数的参数是一个对象,并且是值传递方式。
- 函数的返回值是一个对象,并且是值传递方式。
赋值运算符重载函数
使用operator关键字对运算符重载,其他函数一样,重载运算符有一个返回类型和一个参数列表。
Box operator=(const Box&);
两个类对象进行运算时,若未定义显式赋值运算符重载函数,编译器隐式构建一个运算符重载函数,将两个对象的所有变量进行赋值。用operator关键字可以自定义显式运算符重载函数。
拷贝构造函数和赋值运算符重载函数的区别
- 用一个已存在的对象去构造一个不存在的对象(构造之前不存在),就是拷贝构造。
- 用一个已存在的对象去覆盖另一个已存在的对象,就是赋值运算。
- 拷贝构造函数从一个已经存在的变量来初始化一个新声明的变量,不需要清除现有的值(因为是新创建,所以没有现有值)。
- 拷贝构造函数没有返回值。
- 赋值运算符return *this。
程序分析
上面程序的运行结果
在第一行,实例化一个对象,调用构造函数,打印1;
在第二行,用一个对象初始化另外一个对象,调用拷贝构造函数,打印2;
在第三行,与第一行一样,打印1;
在第四行,用一个已存在的对象去覆盖另一个已存在的对象,赋值运算符重载,调用类中已定义的赋值运算符重载函数,打印3。
命名空间
作用
作为附加信息来区分不同库中名称相同的类,函数,变量。本质上,命名空间就是定义了一个范围。
创建及使用方法
定义命名空间
namespace namespace_name {
// 代码声明
}
调用命名空间的函数或变量
std::cout << endl;//namespace_name::func()或namespace_name::value
实例
#include<iostream>
using namespace std;
namespace first_space{
int a = 3;
}
namespace second_space{
int a = 30;
}
int main()
{
cout<<first_space::a<<endl;
cout<<second_space::a<<endl;
return 0;
}
运行效果
c++预定义宏
宏 | 描述 |
__LINE __ | 编译时包含当前行号 |
__FILE __ | 编译时包含当前文件名 |
__DATE __ | 这会包含一个形式为 month/day/year 的字符串,它表示把源文件转换为目标代码的日期。 |
__TIME __ | 这会包含一个形式为 hour:minute:second 的字符串,它表示程序被编译的时间。 |
异常处理
异常提供了一种转移程序控制权的方式。C++ 异常处理涉及到三个关键字:try、catch、throw。
- throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
- catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
- try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。
抛出异常
可以使用 throw 语句在代码块中的任何地方抛出异常。throw 语句的操作数可以是任意的表达式,表达式的结果的类型决定了抛出的异常的类型。
**eg:**对除法除数为零的情况抛出一个字符串类型的异常:
double division(int a,int b)
{
if(b == 0)
{
throw "divisor is zero!";
}
return (a/b);
}
捕获异常
catch 块跟在 try 块后面,用于捕获异常。可以指定想要捕捉的异常类型,这是由 catch 关键字后的括号内的异常声明决定的。让 catch 块能够处理 try 块抛出的任何类型的异常,则必须在异常声明的括号内使用省略号 …。
#include<iostream>
using namespace std;
double division(int a,int b)
{
if(b == 0)
{
throw "divisor is zero!";
}
return (a/b);
}
int main()
{
int x=6,y=2,z=0;
double result=0;
try{
result = division(x,y);
cout<<result<<endl;
result = division(x,z);
cout<<result<<endl;
}catch(const char *msg){
cerr<<msg<<endl;
}
result = division(x,y);
cout<<result<<endl;
return 0;
}
运行效果
在抛出错误后,捕获异常进行处理,程序不会退出,仍然正常运行。
标准异常
标准异常的层次结构
异常 | 描述 |
std::exception | 所有标准c++异常的父类 |
std::bad_alloc | 通过new抛出 |
std::bad_cast | 通过dynamic_cast抛出 |
std::bad_exception | 用于处理c++程序中无法预期的异常 |
std::bad_typeid | 通过typeid抛出 |
std::logic_error | 可以通过读取代码检测到的异常 |
std::domain_error | 使用一个无效的数学域时抛出 |
std::invalid_argument | 使用了无效的参数时,抛出异常 |
std::length_error | 创建了太长的string时抛出 |
std::out_of_range | 通过方法抛出,eg:std::vector和std::bitset<>::operator |
std::runtime_error | 理论上不可以通过读取代码来检测到的异常 |
std::overflow_error | 当发生数学上溢时抛出 |
std::range_error | 当尝试存储超出范围时抛出 |
std::underflow_error | 当发生数学下溢时抛出 |
可以通过继承和重载exception类来定义新的异常。
what()函数是异常类提供的一个公共方法,它已被所有子异常类重载,返回异常的原因。
文件IO,文件流
文件流的三个数据类型
数据类型 | 描述 |
ofstream | 该数据类型表示输出文件流,用于创建文件并向文件中写入信息 |
ifstream | 该数据类型表示输入文件流,用于从文件读取信息 |
fstream | 文件流,具备ifstream和ofstream的功能 |
文件处理必须包含头文件iostream和fstream;
文件流操作
open()函数
void open(const char *filename,ios::openmode mode);
mode标志:
模式标志 | 描述 |
ios::app | 追加模式。所有写入都追加到文件尾 |
ios::ate | 文件打开后定位到文件末尾 |
ios::in | 打开文件用于读取 |
ios::out | 打开文件用于写入 |
ios::trunc | 若文件已存在,则将文件长度设为零 |
写入文件:
使用流插入运算符(<<)向文件写入;
读取文件
使用流提取运算符(>>)从文件读取;
关闭文件
void close();
文件读写实例
对文件写入:
#include<iostream>
#include<fstream>
using namespace std;
int main()
{
string str("file fstream test!");
fstream fs;
fs.open("./file.txt",ios::out);
fs<<str<<endl;
fs.close();
return 0;
}
运行效果
读取刚刚创建的文件:
#include<iostream>
#include<fstream>
using namespace std;
int main()
{
char ch;
fstream fs;
fs.open("./file.txt",ios::in);
while(!fs.eof())
{
fs.get(ch);
cout<<ch;
}
fs.close();
return 0;
}
运行效果
动态内存
c++的程序中的堆栈
c++程序中的内存分为两个部分:
- 栈:在函数内部声明的所有变量都将占用栈内存。
- 堆:程序中未使用的内存,在程序运行时可用于动态分配内存。
new与delete
#include <iostream>
using namespace std;
int main ()
{
double* pvalue = NULL; // 初始化为 null 的指针
pvalue = new double; // 为变量请求内存
*pvalue = 29494.99; // 在分配的地址存储值
cout << "Value of pvalue : " << *pvalue << endl;
delete pvalue; // 释放内存
return 0;
}
数组的内存分配:
char* pvalue = NULL; // 初始化为 null 的指针
pvalue = new char[20]; // 为变量请求内存
delete [] pvalue; // 删除 pvalue 所指向的数组
对象的动态内存分配:
#include <iostream>
using namespace std;
class Box
{
public:
Box() {
cout << "调用构造函数!" <<endl;
}
~Box() {
cout << "调用析构函数!" <<endl;
}
};
int main( )
{
Box* myBoxArray = new Box[4];
//为4个对象分配内存,会调用4次构造函数,删除对象时,也会调用4次析构函数
delete [] myBoxArray; // 删除数组
return 0;
}
模板、泛型编程
模板的概念
模板是一种使用无类型参数来产生一系列函数或类的机制。通过模板可以产生类或函数的集合,使它们操作不同的数据类型,从而避免需要为每一种数据类型产生一个单独的类或函数。
模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型
的方式编写代码。
模板是创建泛型类或函数的蓝图或公式。
函数模板
模板函数的一般形式:
template <class type> ret-type func-name(parameter list)
{
//函数体;
}
//type 是函数所使用的数据类型的占位符名称。这个名称可以在函数定义中使用。
实例
#include<iostream>
#include<string>
using namespace std;
template <typename T>
T Max(T a,T b)
{
return a>b?a:b;
}
int main()
{
int i1=3;
int i2=4;
cout<<"int:max num="<<max(i1,i2)<<endl;
float f1=2.56;
float f2=9.88;
cout<<"int:max num="<<max(f1,f2)<<endl;
string s1="hello";
string s2="world";
cout<<"int:max num="<<max(s1,s2)<<endl;
return 0;
}
运行效果
类模板
泛型类的一般形式:
template <class type> class class-name
{
//类的数据和成员函数声明;
}
//type 是占位符类型名称,可以在类被实例化的时候进行指定。可以使用一个逗号分隔的列表来定义多个泛型数据类型。
类模板实现栈实例
#include<iostream>
#include<vector>
#include<stdexcept>
using namespace std;
template <class T>
class Stack{
private:
vector<T> elems;
public:
void push(T const&elem)//入栈
//&传引用减少复制开销,const不改变传入参数的值
{
elems.push_back(elem);
}
void pop() //出栈
{
if(elems.empty())
throw out_of_range("Stack::pop():empty stack!");
elems.pop_back();
}
T top() const //返回栈顶元素,+const表示不可改变数据成员
{
if(elems.empty())
throw out_of_range("Stack::top():empty stack!");
return elems.back();
}
bool empty() const{
return elems.empty();
}
};
int main()
{
Stack<double> doubleStack;//double型栈
Stack<string> stringStack;//string型栈
try{
//double型栈操作
doubleStack.push(8.23);
doubleStack.push(1.22);
cout<<"doubleStack top:"<<doubleStack.top()<<endl;
doubleStack.pop();
cout<<"doubleStack top:"<<doubleStack.top()<<endl;
//string型栈操作
stringStack.push("hello");
cout<<"stringStack top:"<<stringStack.top()<<endl;
stringStack.pop();
stringStack.pop();
cout<<"stringStack top:"<<stringStack.top()<<endl;
}catch(exception const& e){
cerr << e.what()<<endl;
return -1;
}
return 0;
}
运行效果