目录

  • 内联函数
  • 简介
  • 与带参数宏的区别
  • 引用
  • 创建引用
  • 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 语言转java c语言转c++简明教程_Stack

C++ 引用 vs 指针

引用和指针很容易混淆,主要有以下不同:

  1. 不存在空引用,引用必须连接到一块合法内存;
  2. 引用初始化后,只能指向初始化的对象,不能像指针一样在需要的时候更改指向的对象;
  3. 引用创建时必须初始化,指针可以在任何时候初始化。

把引用做参数

与指针类似,直接用引用操作内存,调用函数返回后对变量的操作依然有效
实例:

#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;
}

c 语言转java c语言转c++简明教程_#include_02

把引用做返回值

当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边。

#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;
}

c 语言转java c语言转c++简明教程_#include_03


注意:

返回超出函数作用域的引用是不合法的,但是可以返回一个用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。

程序分析

上面程序的运行结果

c 语言转java c语言转c++简明教程_ios_04


c 语言转java c语言转c++简明教程_#include_05


在第一行,实例化一个对象,调用构造函数,打印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 语言转java c语言转c++简明教程_#include_06

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;
}

运行效果

c 语言转java c语言转c++简明教程_Stack_07


在抛出错误后,捕获异常进行处理,程序不会退出,仍然正常运行。

标准异常

标准异常的层次结构

c 语言转java c语言转c++简明教程_c 语言转java_08

异常

描述

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;
}

运行效果

c 语言转java c语言转c++简明教程_c 语言转java_09


读取刚刚创建的文件:

#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 语言转java c语言转c++简明教程_#include_10

动态内存

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;
}

运行效果

c 语言转java c语言转c++简明教程_ios_11

类模板

泛型类的一般形式:

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;
}

运行效果

c 语言转java c语言转c++简明教程_Stack_12