[学习笔记-C++篇]day8 文件和流-Web编程

努力时毫不质疑,结束时绝不留恋。
更新计划了,9+1,第10天的教程会同步更新
感谢大佬几款优秀的支持C、C++在线编译器


stage1——10天入门阶段

教程网站:C++ 教程
在线编译器:compile c++ gcc online
刷题网站:阶段1第一关:基本数据类型

day8 planA
教程3,刷题3(5),复习3(1)
教程完成度100%,刷题完成度0%,复习完成度100%
主要原因:摸鱼


Q&A

1.文件和流

iostream标准库提供了用于分别从标准输入读取流和向标准输出写入流。
fstream则包含了从文件读取流和向文件写入流。

ofstream : 该数据类型表示输出文件流,用于创建文件并向文件写入信息
ifstream : 该数据类型表示输入文件流,用于从文件读取信息
fstream  : 该数据类型通常表示文件流,且同时具有 ofstream 和 ifstream 两种功能,这意味着它可以创建文件,向文件写入信息,从文件读取信息

1.1 打开文件

如果要打开文件并对文件写,用fstreamofstream;如果只读文件,用ifstream
注意,这里ofstream指的是从内存output到硬盘(文件),ifstream指的是从硬盘input到内存缓冲区。主语始终是内存。

打开文件用open()函数,函数是fstreamifstreamofstream对象的一个成员。
注意,open()中输入的必须是字符类型,如果是字符串string要转换

void open(const char *filename, ios::openmode mode);//打开的文件名(指针类型)和打开方式

下面是几种打开方式,可以把两种或两种以上的模式结合使用:

ofstream outfile;//定义一个ofstream类型的对象,但是一个空对象
outfile.open("file.dat", ios::out | ios::trunc );//open的时候才指明对象所对应的文件

ifstream  afile;
afile.open("file.dat", ios::out | ios::in );

string s="myfile.dat";
ifstream infile;
infile.open(s.c_str());

ps.疑问,不是说ifstream只能用于读入吗,那为什么用open函数的时候,也可以指定模式是写出out呢?

在这里插入图片描述

1.2 关闭文件

open对应,close(),同样是fstreamifstreamofstream对象的一个成员。

void close();
//eg.
outfile.close();

1.3 读入和写出文件

使用流插入运算符<<向文件写入信息,就像使用该运算符输出信息到屏幕上一样。唯一不同的是,在这里您使用的是ofstreamfstream对象,而不是cout对象。
使用流提取运算符>>从文件读取信息,就像使用该运算符从键盘输入信息一样。唯一不同的是,在这里您使用的是ifstreamfstream对象,而不是cin对象。

路径要用\

#include <fstream>
#include <iostream>
using namespace std;
 
int main ()
{
    
   char data[100];
 
   // 以写模式打开文件
   ofstream outfile;
   outfile.open("afile.dat");//注意!!!这边如果路径较长不要直接用,这是反义符,要用\
 
   cout << "Writing to the file" << endl;
   cout << "Enter your name: "; 
   cin.getline(data, 100);
 
   // 向文件写入用户输入的数据
   outfile << data << endl;
 
   cout << "Enter your age: "; 
   cin >> data;
   cin.ignore();
   
   // 再次向文件写入用户输入的数据
   outfile << data << endl;
 
   // 关闭打开的文件
   outfile.close();
 
   // 以读模式打开文件
   ifstream infile; 
   infile.open("afile.dat"); 
 
   cout << "Reading from the file" << endl; 
   infile >> data; 
 
   // 在屏幕上写入数据
   cout << data << endl;
   
   // 再次从文件读取数据,并显示它
   infile >> data; 
   cout << data << endl; 
 
   // 关闭打开的文件
   infile.close();
 
   return 0;
}

参考getline()与cin.getline()函数用法详解
此外,从键盘键入字符串读取用的是cin.getline(保存到的数据变量, 接受的字符个数, 终止字符)。一般不写终止符默认 。

cin.getline(char *ch,int num,char f);
//eg.
char ch[50];
cin.getline(ch,50,'!');

则输入hel!o的话,只会在ch中保存hel的内容

此外,getline()函数是用来输入字符串的

istream &getline( char *buffer, streamsize num, char delim );//形参分别为输入流,输入到的字符串变量,终止字符
//eg.

#include<iostream>
#include<string>
using namespace std;
 
int main()
{
	string line;
	getline(cin,line,'?');//不写第3个参数默认
结束
	
	return 0;
}

1.4 文件位置指针

istreamostream都提供了用于重新定位文件位置指针的成员函数。这些成员函数包括关于istreamseekg(“seek get”)和关于ostreamseekp(“seek put”)。

seekgseekp的参数通常是一个长整型。第二个参数可以用于指定查找方向。查找方向可以是ios::beg(默认的,从流的开头开始定位),也可以是ios::cur(从流的当前位置开始定位),也可以是ios::end(从流的末尾开始定位)。

文件位置指针是一个整数值,指定了从文件的起始位置到指针所在位置的字节数。

// 定位到 fileObject 的第 n 个字节(假设是 ios::beg)
fileObject.seekg( n );
 
// 把文件的读指针从 fileObject 当前位置向后移 n 个字节
fileObject.seekg( n, ios::cur );
 
// 把文件的读指针从 fileObject 末尾往回移 n 个字节
fileObject.seekg( n, ios::end );
 
// 定位到 fileObject 的末尾
fileObject.seekg( 0, ios::end );

2.异常处理

C++ 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。
异常提供了一种转移程序控制权的方式。C++ 异常处理涉及到三个关键字:trycatchthrow

throw: 当问题出现时,程序会抛出一个异常。这是通过使用throw关键字来完成的。
catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch关键字用于捕获异常。
try: try块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个catch块.

如果有一个块抛出一个异常,捕获异常的方法会使用trycatch关键字。try块中放置可能抛出异常的代码,被称为保护代码
如果try块在不同的情境下会抛出不同的异常,这个时候可以尝试罗列多个catch语句,用于捕获不同类型的异常。

try
{
   // 保护代码
}catch( ExceptionName e1 )//捕获一个类型为 ExceptionName 的异常
{
   // catch 块
}catch( ExceptionName e2 )
{
   // catch 块
}catch( ExceptionName eN )
{
   // catch 块
}

2.1 抛出异常

使用throw语句在代码块中的任何地方抛出异常。throw语句的操作数可以是任意的表达式,表达式的结果的类型决定了抛出的异常的类型。

像是一种专门为程序异常给的cerr一样。

double division(int a, int b)
{
   if( b == 0 )
   {
      throw "Division by zero condition!";
   }
   return (a/b);
}

2.2 捕获异常

catch块跟在try块后面,用于捕获异常。您可以指定想要捕捉的异常类型,这是由catch关键字后的括号内的异常声明决定的。
程序和前前面一样。
如果您想让catch块能够处理try块抛出的任何类型的异常,则必须在异常声明的括号内使用省略号...

try
{
   // 保护代码
}catch(...)
{
  // 能处理任何异常的代码
}

总体的例子:

#include <iostream>
using namespace std;
 
double division(int a, int b)
{
   if( b == 0 )
   {
      throw "Division by zero condition!";//抛出异常值
   }
   return (a/b);
}
 
int main ()
{
   int x = 50;
   int y = 0;
   double z = 0;
 
   try {
     z = division(x, y);
     cout << z << endl;
   }catch (const char* msg) {//catch去捕获之前跑出2的异常值,类型要和异常值一样
     cerr << msg << endl;
   }
 
   return 0;
}


输出------------
Error(s):
Division by zero condition!

由于我们抛出了一个类型为const char*的异常,因此,当捕获该异常时,我们必须在catch块中使用const char*

2.3 C++的标准异常

C++ 提供了一系列标准的异常,定义在 中。
在这里插入图片描述
说明贴不了图,见菜鸟。

2.4 定义新的异常

可以通过继承和重载exception类来定义新的异常。

#include <iostream>
#include <exception>
using namespace std;
 
struct MyException : public exception
{
  const char * what () const throw ()//和之前的重载样式有点不一样
  {
    return "C++ Exception";
  }
};
 
int main()
{
  try
  {
    throw MyException();
  }
  catch(MyException& e)
  {
    std::cout << "MyException caught" << std::endl;
    std::cout << e.what() << std::endl;
  }
  catch(std::exception& e)
  {
    //其他的错误
  }
}


输出-------------
MyException caught
C++ Exception

3.动态内存

有2种内存:
**
:在函数内部声明的所有变量都将占用栈内存。
:这是程序中未使用的内存,在程序运行时可用于动态分配内存。
**

在 C++ 中,您可以使用特殊的运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。这种运算符即new运算符。

如果您不再需要动态分配的内存空间,可以使用delete运算符,删除之前由new运算符分配的内存。

建议尽量不要使用malloc()函数。new不只是分配了内存,它还创建了对象。

3.1 newdelete

可以对包括数组在内的任意内置的数据类型,也可以是包括类或结构在内的用户自定义的任何数据类型。

new data-type;
delete data-type;

但肯定是对指针进行分配。一般就是先定义一个类型的指针,然后对这个指针用new分配地址,地址长度是和类型相关的,所以要加上类型。delete的时候就直接加指针就行。

double* pvalue  = NULL; // 初始化为 null 的指针
pvalue  = new double;   // 为变量请求内存

3.2 数组的动态内存分配

什么类型的数组都是一样的。注意定义和删除的方式!!!

一维数组:
char* pvalue  = NULL;   // 初始化为 null 的指针
pvalue  = new char[20]; // 为变量请求内存
或者
int *p=new int[20];

delete [] pvalue;        // 删除 pvalue 所指向的数组

二维数组:

```cpp
int **p;
// 假定数组第一维长度为 m, 第二维长度为 n
// 动态分配空间
p=new int *[m];
for(int i=0;i<m;i++)
{
  p[i]=new int[n];
}

for(int i=0;i<n;i++)
{
  delete [] p[m];
}
delete [] p;

反正就是要一个维度一个维度的分内存,分的时候要注意维度;删除的时候就把定义的顺序反过来,后分配先删除。

include <iostream>

using namespace std;

int main ()
{
   int **p;
    
   p=new int*[2];
    for(int i=0;i<2;i++)
    {
        p[i]=new int [3];
    }
    for(int i=0;i<2;i++)
    {
        for(int j=0;j<3;j++)
        {
            p[i][j]=i*j;
            cout << p[i][j] << endl;
        }
    }
    
    for(int j=0;j<3;j++)
    {
        delete [] p[2];
    }
   delete [] p;
 
   return 0;
}

三维数组类似的。

int ***array;
// 假定数组第一维为 m, 第二维为 n, 第三维为h
// 动态分配空间
array = new int **[m];
for( int i=0; i<m; i++ )
{
    array[i] = new int *[n];
    for( int j=0; j<n; j++ )
    {
        array[i][j] = new int [h];
    }
}
//释放
for( int i=0; i<m; i++ )
{
    for( int j=0; j<n; j++ )
    {
        delete[] array[i][j];
    }
    delete[] array[i];
}
delete[] array;

3.3 对象的动态内存分配

一样的。

#include <iostream>
using namespace std;
 
class Box
{
   public:
      Box() { 
         cout << "调用构造函数!" <<endl; 
      }
      ~Box() { 
         cout << "调用析构函数!" <<endl; 
      }
};
 
int main( )
{
   Box* myBoxArray = new Box[4];
 
   delete [] myBoxArray; // 删除数组
   return 0;
}

如果不是对数组,是对类:

#include <iostream>

using namespace std;

class box
{
    public:
    int a;
    box(int aa);
    int get()
    {
        return a;
    }
};

box :: box(int aa)
{
    a=aa;
}

int main ()
{
   box *b=new box(5);
   cout << b->get() << endl;
    
   delete b;
 
   return 0;
}

**
注意2点:
1)如果构造函数有参数,那么在分配地址的时候也要使用相应的定义,直接赋参数
2)定义的毕竟是指针,所以使用类的函数的时候还是要用->
**

4.命名空间

命名空间可作为附加信息来区分不同库中相同名称的函数、类、变量等。

4.1 定义命名空间

namespace namespace_name {
   // 代码声明
}

namespace_name::code;  // code 可以是变量或函数


#include <iostream>

using namespace std;

namespace name
{
    void out()
    {
        cout << "name 1." << endl;
    }
}
namespace space
{
    void out()
    {
        cout << "name 2." << endl;
    }
}

int main ()
{
   name::out();
    
   space::out();
 
   return 0;
}

您可以使用using namespace指令,这样在使用命名空间时就可以不用在前面加上命名空间的名称。这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。

一个命名空间的各个组成部分可以分散在多个文件中。所以,如果命名空间中的某个组成部分需要请求定义在另一个文件中的名称,则仍然需要声明该名称。

4.2 嵌套命名空间

namespace namespace_name1 {
   // 代码声明
   namespace namespace_name2 {
      // 代码声明
   }
}

// 访问 namespace_name2 中的成员
using namespace namespace_name1::namespace_name2;
 
// 访问 namespace:name1 中的成员
using namespace namespace_name1;

5.模板

模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。

模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。

每个容器都有一个单一的定义,比如向量,我们可以定义许多不同类型的向量,比如vector <int>vector <string>

5.1 函数模板

粗浅理解,模板是重载函数的整合。

template <typename type> 
type func-name(parameter list)
{
   // 函数的主体
}
type 是函数所使用的数据类型的占位符名称。
parameter list 的类型也要用type。

简单理解一下,定义模板的时候一定要有的是template <typename T>,T是形参和返回值的类型。

#include <iostream>
#include <string>
 
using namespace std;
 
template <typename T>
inline T const& Max (T const& a, T const& b) 
{ ?
   ?return a < b ? b:a; 
} 
int ?main ()
{ ?
   ?int i = 39;
   ?int j = 20;
   ?cout << "Max(i, j): " << Max(i, j) << endl;  ?
   ?double f1 = 13.5; 
   ?double f2 = 20.7; 
   ?cout << "Max(f1, f2): " << Max(f1, f2) << endl;  ?
   ?string s1 = "Hello"; 
   ?string s2 = "World"; 
   ?cout << "Max(s1, s2): " << Max(s1, s2) << endl;  ?

5.2 类模板

和函数模板是一样的。但要注意:
1)typename更改为class
2)在类的定义中所有变量类型都要用T
3)如果在类外定义函数,那么一定要记得加上template <class T>
4)调用类模板的时候,注意要标注出使用的变量类型,比如Stack<int>

template <class type> 
class class-name {
//主体
};


#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>
 
using namespace std;
 
template <class T>
class Stack { 
  private: 
    vector<T> elems;     // 元素 
 
  public: 
    void push(T const&);  // 入栈
    void pop();               // 出栈
    T top() const;            // 返回栈顶元素
    bool empty() const{       // 如果为空则返回真。
        return elems.empty(); 
    } 
}; 
 
template <class T>
void Stack<T>::push (T const& elem) 
{ 
    // 追加传入元素的副本
    elems.push_back(elem);    
} 
 
template <class T>
void Stack<T>::pop () 
{ 
    if (elems.empty()) { 
        throw out_of_range("Stack<>::pop(): empty stack"); 
    }
    // 删除最后一个元素
    elems.pop_back();         
} 
 
template <class T>
T Stack<T>::top () const 
{ 
    if (elems.empty()) { 
        throw out_of_range("Stack<>::top(): empty stack"); 
    }
    // 返回最后一个元素的副本 
    return elems.back();      
} 
 
int main() 
{ 
    try { 
        Stack<int>         intStack;  // int 类型的栈 
        Stack<string> stringStack;    // string 类型的栈 
 
        // 操作 int 类型的栈 
        intStack.push(7); 
        cout << intStack.top() <<endl; 
 
        // 操作 string 类型的栈 
        stringStack.push("hello"); 
        cout << stringStack.top() << std::endl; 
        stringStack.pop(); 
        stringStack.pop(); 
    } 
    catch (exception const& ex) { 
        cerr << "Exception: " << ex.what() <<endl; 
        return -1;
    } 
}

题外话:pop_back()back()区别,前者是删掉最后一个(出栈),后者是返回最后一个值。

6.预处理器

所有的预处理器指令都是以井号#开头,结尾没有;
#include#define#if#else#line等。

6.1 define

#define预处理指令用于创建符号常量。该符号常量通常称为宏。

#define macro-name replacement-text 

//eg.
#define PI 3.14

6.2 参数宏

像是快速定义一个函数。

#include <iostream>
using namespace std;
 
#define MIN(a,b) (a<b ? a : b)
 
int main ()
{
   int i, j;
   i = 100;
   j = 30;
   cout <<"较小的值为:" << MIN(i, j) << endl;
 
    return 0;
}

有点像内联inline,只能用一句指令,但实际上inline多加几句也是没关系的,只是不被认为是内联。

6.3 条件编译

条件编译,有选择地对部分程序源代码进行编译。

条件预处理器:
#ifdef NULL
   #define NULL 0
#endif

调试开关:
#ifdef DEBUG
   cerr <<"Variable x = " << x << endl;
#endif
如果在指令 #ifdef DEBUG 之前已经定义了符号常量 DEBUG,则会对程序中的 cerr 语句进行编译。

调试程序:
#if 0
   不进行编译的代码
#endif


输出------------
Trace: Inside main function
The minimum is 30
Trace: Coming out of main function

6.4 # 和 ## 运算符

###预处理运算符在 C++ 和 ANSI/ISO C 中都是可用的。

#运算符会把 replacement-text 令牌转换为用引号引起来的字符串。

#include <iostream>
using namespace std;
 
#define MKSTR( x ) #x
 
int main ()
{
    cout << MKSTR(HELLO C++) << endl;//会把括号里的东西当做字符串输出,即转换为cout << "HELLO C++" << endl;
 
    return 0;
}


输出-----------
HELLO C++

##运算符用于连接两个令牌

#include <iostream>
using namespace std;
 
#define concat(a, b) a ## b
int main()
{
   int xy = 100;
   
   cout << concat(x, y);//转换为cout << xy;
   return 0;
}


输出----------------
100

6.5 预宏定义

在这里插入图片描述

#include <iostream>
using namespace std;
 
int main ()
{
    cout << "Value of __LINE__ : " << __LINE__ << endl;//当前代码所在行
    cout << "Value of __FILE__ : " << __FILE__ << endl;
    cout << "Value of __DATE__ : " << __DATE__ << endl;
    cout << "Value of __TIME__ : " << __TIME__ << endl;
 
    return 0;
}


输出------------------
Value of __LINE__ : 6
Value of __FILE__ : test.cpp
Value of __DATE__ : Feb 28 2011
Value of __TIME__ : 18:52:48

7.信号处理

在 UNIX、LINUX、Mac OS X 或 Windows 系统上,可以通过按Ctrl+C产生中断。

有些信号不能被程序捕获,但是下表所列信号可以在程序中捕获,并可以基于信号采取适当的动作。这些信号是定义在 C++ 头文件<csignal>中。

这里看的不太明白,还是用到的时候再看菜鸟吧。

8.多线程

多线程是多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序。一般情况下,两种类型的多任务处理:基于进程和基于线程
基于进程的多任务处理是程序的并发执行;
基于线程的多任务处理是同一程序的片段的并发执行。

(进程可以简单的理解为一个可以独立运行的程序单位,它是线程的集合,进程就是有一个或多个线程构成的。而线程是进程中的实际运行单位,是操作系统进行运算调度的最小单位。可理解为线程是进程中的一个最小运行单元。参考多进程和多线程的概念

假设您使用的是 Linux 操作系统,我们要使用 POSIX 编写多线程 C++ 程序。POSIX Threads 或 Pthreads 提供的 API 可在多种类 Unix POSIX 系统上可用,比如 FreeBSD、NetBSD、GNU/Linux、Mac OS X 和 Solaris。

8.1 创建线程

#include <pthread.h>
pthread_create (thread, attr, start_routine, arg) 

· thread:指向线程标识符指针。
· attr:一个不透明的属性对象,可以被用来设置线程属性。您可以指定线程属性对象,也可以使用默认值 NULL。
· start_routine:线程运行函数起始地址,一旦线程被创建就会执行。
· arg:运行函数的参数。它必须通过把引用作为指针强制转换为 void 类型进行传递。如果没有传递参数,则使用 NULL。

注意:
1)创建的时候输入的是指针,所以如果pthread_t p,调用的时候要加&
2)创建的时候用pthread_create,其中调用函数的时候只要函数名,不用()
3)线程函数定义的时候要说明形参,没有形参也要加void *args;定义一定最后要加上return 0;
4)创建线程有返回值,创建线程成功时,函数返回 0,若返回值不为 0 则说明创建线程失败

8.2 终止线程

#include <pthread.h>
pthread_exit (status) 

pthread_exit 用于显式地退出一个线程。通常情况下,pthread_exit() 函数是在线程完成工作后无需继续存在时被调用。

如果 main() 是在它所创建的线程之前结束,并通过 pthread_exit() 退出,那么其他线程将继续执行。否则,它们将在 main() 结束时自动被终止。

#include <iostream>
// 必须的头文件
#include <pthread.h>
 
using namespace std;
 
#define NUM_THREADS 5
 
// 线程的运行函数
void* say_hello(void* args)
{
    cout << "Hello Runoob!" << endl;
    return 0;
}
 
int main()
{
    // 定义线程的 id 变量,多个变量使用数组
    pthread_t tids[NUM_THREADS];
    for(int i = 0; i < NUM_THREADS; ++i)
    {
        //参数依次是:创建的线程id,线程参数,调用的函数,传入的函数参数
        int ret = pthread_create(&tids[i], NULL, say_hello, NULL);
        if (ret != 0)
        {
           cout << "pthread_create error: error_code=" << ret << endl;
        }
    }
    //等各个线程退出后,进程才结束,否则进程强制结束了,线程可能还没反应过来;
    pthread_exit(NULL);
}

ps.疑问,运行的时候说pthread_create没有定义?为什么?因为在线编译器?不清楚。

9.Web编程

暂时暂时先略过,看菜鸟。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哒佬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值