logo头像
Snippet 博客主题

C++

c++特性学习

命名空间

在C++编程中,在程序的头两行,经常会有这样一句话

1
using namespace std;

我们平常使用的cin、cout语句都是借用了命名空间std中的,如果不直接引入命名空间,每一次我们都需要进行这样的调用:

1
2
std::cin;
std::cout;

这样将会显得非常琐碎,所以会用上开头的一整句放在程序前段,便于使用其中的内容

在C++中,头文件不应该包含命名空间声明

string类型

在c++中,string类型表示可变长的字符序列,同时当我们需要用到string类时,需要加上

1
2
#include<string>
using std:string;

直接初始化和拷贝初始化

读入含有空格的字符串

1
2
string a;
getline(cin,a);

指针


引用

引用(reference)为对象起了另外一个名字,引用类型引用(refers to)另外一种类型。通过将声明符写成&d的形式来定义引用类型,其中d时声明的变量名

1
2
3
int val=1024;
int &refVal=ival;//refVal指向ival
int &ref=Val2;//报错:引用必须被初始化

引用并非对象,相反的,它只是为一个已经存在的对象所起的另外一个名字

1
2
3
4
5
6
7
8
9
//定义了一个引用之后,对其进行的所有操作都是在与之绑定的对象上进行的
refVal=2;//把2赋给refVal指向的对象,此处即是赋给了ival
int ii=refVal;//与ii=ival执行结果一样

//允许在一条语句中定义多个引用,其中每个引用标识符都必须以符号&开头
int i=1024,i2=2048;//i和i2都是int
int &r=i,r2=i2;//r是一个引用,与i绑定在一起,r2是int
int i3=1024,&ri=i3;//i3是int,ri是一个引用,与i3绑定在一起
int &r3=i3,&r2=i2;//r3和r4都是引用

指针

指针(pointer)是“指向”(point to),与引用类似,指针也实现了对其他对象的间接访问,但是1.指针本身就是一个对象,允许对指针赋值和拷贝。2.指针无须在定义时赋初值。和其他内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值

1
2
3
4
5
6
7
8
9
10
int *ip1,*ip2;//ip1和ip2都是指向int型对象的指针
int val=42;
int *p=&ival;//p存放变量ival的地址,或者说p是指向变量ival的指针

double dval;
double *pd=&dval;//正确,初始值是double型对象的地址
double *pd2=pd;//正确:初始值是指向double对象的指针

int *pi=pd;//错误:指针pi的类型和pd的类型不匹配
pi=&dval;//错误:试图把double型对象的地址赋给int型指针

指针值

指针的值(即地址)应属下列4种状态之一:

  1. 指向一个对象
  2. 指向紧邻对象所占空间的下一个位置
  3. 空指针,意味着指针没有指向任何对象
  4. 无效指针,也就是上述情况之外的其他值

利用指针访问对象

1
2
3
4
5
6
int ival=42;
int *p=&ival; //p存放着变量ival的地址,或者说p是指向变量ival的指针
cout<<*p; //由符号*得到指针p所指的对象

*p=0; //由符号*得到指针p所指的对象,即可经由p位变量ival赋值
cout<<*p;//输出0

空指针

1
2
3
int *p1=nullptr;
int *p2=0;
int *p3=NULL;

建议:使用指针的时候,一定要进行初始化,使用未经初始化的指针是引发运行时错误的一大原因

指针赋值

1
2
3
4
5
6
7
8
9
10
int i=42;
int *pi=0;//pi被初始化,但没有指向任何对象
int *pi2=&i;//pi2被初始化,存有i的地址
int *pi3;//如果pi3定义于块内,则pi3的值是无法确定的

pi3=pi2;//pi3和pi2指向同一个对象
pi2=0;//现在pi2不指向任何对象了

pi=&ival;//pi的值被改变,现在pi指向了ival
*pi=0;//ival的值被改变,指针pi没有改变,也就是*pi(也就是指针pi指向的那个对象)发生改变

void*指针

void是一种特殊的的指针类型,可用于存放任意对象的地址。一个void指针存放着一个地址,这一点和其他指针类似

1
2
3
4
double obj=3.14,*pd=&obj;
//正确,void*能存放任意类型对象的地址
void *pv=&obj;//obj可以是任意类型的对象
pv=pd;//pv可以存放任意类型的指针

指向指针的指针

一般来说,声明符中修饰符的个数并没有限制,当有多个修饰符连写在一起时,按照其逻辑关系详加解释即可。

1
2
3
int ival=1024;
int *pi=&ival;//pi指向一个int型的数
int **ppi=&pi;//ppi指向一个int型的指针

指向指针的引用

引用本身不是一个对象,因此不能定义指向引用的指针。但指针是对象,所以存在对指针的引用

1
2
3
4
5
6
int i=42;
int *p;//p是一个int型指针
int *&r=p;//r是一个对指针p的引用

r=&i;//r引用了一个指针,因此给r赋值&i就是令p指向i
*r=0;//解引用r得到i,也就是p指向的对象,将i的值改为0

关于指针

​ 关于指针,有这样两个符号出现的频率非常之高:

​ *、&

对于他们的用法,我们有必要做一个非常深刻的研究,首先我们明确两点:

  1. &代表引用,即取址运算符
  2. *代表声明此变量为指针变量,即是指向某个地址的变量

接下来举一个最简单的例子

1
2
int a=1;//声明一个整型变量a
int *aptr=&a;//声明一个整型指针变量aptr,指向整型变量a的地址(准确的来说应该是将整型变量a的在内存中的位置赋值给aptr)

最基本的,我们可以通过指针对指向的变量进行操作:

1
2
3
cout<<a<<endl;//结果为1
*aptr=2;
cout<<a<<endl;//结果为2,但是aptr本身的值并没有发生改变,但是将指向的整型变量a的值改变了

const限定符


有时我们希望定义这样一种变量,它的值不能改变。例如,用一个变量来表示缓冲区的大小。使用变量的好处是当我们觉得缓冲区大小不再合适时,很容易对其进行调整。另一方面,也应随时警惕程序一不小心改变了这个值。为了满足这一要求,可以用关键字const对变量的类型加以限定

1
2
3
4
5
6
const int bufSize=512;//输入缓冲区大小
bufSize=512;//错误,试图向const对象写值

const int i=get_size();//正确:运行时初始化
const int j=42;//正确:编译时初始化
const int k;//错误:k是一个未经初始化的变量

默认状态下,const对象仅在文件内有效

当以编译时初始化的方式定义一个const对象时,就如对bufSize的定义一样:

1
const int bufSize=512//输入缓冲区大小

编译器将在编译过程中把用到该变量的地方都替换成对应的值。也就是说,编译器会找到代码中所有用到bufSize的地方,然后用512替换


某些时候有这样一种const变量,它的初始值不是一个常量表达式,但又确实有必要在文件间共享。这种情况下,我们不希望编译器为每个文件分别生成独立的变量。相反,我们想让这类const对象像其他对象一样工作,也就是说,只在一个文件中定义const,而在其他多个文件中声明并使用它

1
2
3
4
5
//解决方法是加上extern关键字即可
//file_1.cc定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize=fcn();
//file_1.h头文件
extern const int bufSize;//与file_1.cc中定义的bufSize是同一个

初始化和对const的引用

1
2
3
4
5
int i=42;
const int &r1=i;//允许将const int&绑定到一个普通int对象上
const int &r2=42;//正确,r1是一个常量引用
const int &r3=r1*2;//正确:r3是一个常量引用
int &r4=r1*2;//错误:r4是一个普通的非常量引用

必须认识到,常量引用仅对引用可参与的操作作出了限定,对于引用的对象本身是不是一个常量未作限定。因为对象也可能是个非常量,所以允许通过其他途径改变它的值

1
2
3
4
5
int i=42;
int &r1=i;//引用ri绑定对象i
const int &r2=i;//r2也绑定对象i,但是不允许通过r2修改i的值
r1=0//r1并非常量,i的值可以修改为0
r2=0;//错误:无法修改

指向常量的指针(pointer to const)不能用于改变其所指对象的值。要想存放常量对象的地址,只能使用指向常量的地址

1
2
3
4
const double pi=3.14;//pi是个常量,它的值不能改变
double *ptr=&pi;//错误:ptr是一个普通指针
const double *cptr=&pi;//正确:cptr可以指向一个双精度常量
*cptr=42;//错误:不能给*cptr赋值

const指针

指针是对象而引用不是,因此迹象其他对象类型一样,允许把指针本身定为常量。常量指针(const pointer)必须初始化,而且一旦初始化完成,则它的值(也就是存放在指针中的那个地址)就不能再改变了。把*放在const关键字之前用以说明指针是一个常量,这样的书写形式隐含着一层意味,即不变的是指针本身的值而非指向的那个值

1
2
3
4
5
6
7
8
9
10
11
int errNumb=0;
int *const curErr=&errNumb;//curErr将一直指向errNumb
const double pi=3.14159;
const double *const pip=&pi;//pip是一个指向常量对象的常量指针

*pip=2.72;//错误:pip是一个指向常量的指针
//如果curErr所指的对象(也就是errNumb)的值不为0
if(*curErr){
errorHandler();
*curErr=0;//正确:把curErr所指的对象的值重置
}

顶层const

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int i=0;
int *const p1=&i;//不能改变p1的值,这是一个顶层const
const int ci=42;//不能改变ci的值,这是一个顶层const
const int *p2=&ci;//允许改变p2的值,这是一个底层const
const int *const p3=p2;//靠右的const是顶层const,靠左的是底层const
const int &r=ci;//用于声明引用的const都是底层const

i=ci;//正确:拷贝ci的值,ci是一个顶层const,对此操作无影响
p2=p3;//正确:p2和p3指向的对象类型相同,p3顶层const的部分不影响

int *p=p3;//错误:p3包含底层const的定义,而p没有
p2=p3;//正确:p2和p3都是底层const
p2=&i;//正确:int*能转换成const int*
int &r=ci;//错误:普通的int&不能绑定到int常量上
const int &r2=i;//正确const int&可以绑定到一个普通int上

指针本身是一个对象,它又可以指向另外一个对象,因此,指针本身是不是常量以及指针所指的是不是一个常量就是两个相互独立的问题。用名词顶层const(top-level const)表示指针本身是个常量,而用名词底层const(low-level const)表示指针所指的对象是一个常量

更一般的,顶层const可以表示任意的对象是常量,这一点对任何数据类型都适用,如算术类型、类、指针等。底层const则与指针和引用等符合类型的基本类型部分有关。比较特殊的是,指针类型既可以是顶层const也可以是底层const,这一点和其它类型相比区别明显:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int i=0;
int *const p1=&i;//不能改变p1的值,这是一个顶层const
const int ci=42;//不能改变ci的值,这是一个顶层const
const int *p2=&ci;//允许改变p2的值,这是一个底层const
const int *const p3=p2;//靠右的const是顶层const,靠左的是底层const
const int &r=ci;//用于声明引用的const都是底层const\

//拷贝时,顶层const不受什么影响,拷入和拷出对象是否是常量没什么影响
//底层const限制不能忽视,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换。一般来说,非常量可以转换成常量,反之则不行
int *p=p3;//错误:p3包含底层const的定义,而p没有
p2=p3;//正确:p2和p3都是底层const
p2=&i;//正确:int*能转换成const int*
int &r=ci;//错误:普通的int&不能绑定到int常量上
const int &r2=i;//正确:const int&可以绑定到一个普通int上

容器


定义

一个容器就是一些特定类型对象的集合。顺序容器(sequential container)为程序员提供了控制元素存储和访问顺序的能力。这种顺序不依赖于元素的值,而是与元素加入容器时的位置相对应

顺序容器类型

  • vector:可变大小数组,支持快速随机访问,在尾部之外的位置插入或删除元素可能很慢

  • deque:双端队列。支持快速随机访问。在头尾位置插入/删除速度很快

  • list:双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除操作速度都很快

  • forward:单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作都很快

  • array:固定大小数组。支持快速随机访问,不能添加或删除元素

  • string:与vector相似的容器,但专门用于保存字符。随机访问快。在尾部插入/删除速度快


    以下是选择容器时的基本原则

    • 如果没有绝对的理由,使用vector
    • 如果程序有很多小元素且对额外空间开销要求很高,则不要使用list或者forward_list
    • 程序要求随机访问元素,使用vector或者deque
    • 如果要在容器中间位置进行增删操作,使用list或者forward_list
    • 头尾位置增删,使用deque

迭代器

迭代器的元素范围为左闭合区间,标准数学描述为

[begin,end)

使用迭代器访问元素

1
2
3
vector<int>::iterator it;
for(it=vec.begin();it!=vec.end();it++)
cout<<*it<<endl

类型成员

每个容器都定义了多个类型,我们熟悉的有三种:size_type,iterator,const_iterator

1
2
3
4
//iter时通过list<string>定义的一个迭代器类型
list<string>::iterator iter;
//count时通过vector<int>定义的一个difference_type类型
vector<int>::difference_type count;

容器中还有begin和end操作,生成指向容器中第一个元素和尾元素之后位置的迭代器,而成员中也有begin和end类,即为左闭合区间的范围

容器拷贝

两个容器拷贝时,要求两个容器的数据类型和容器类型都要匹配

1
2
3
4
5
6
7
8
9
10
11
12
//每个容器由三个元素,用给定的初始化器进行初始化
list<string> authors={"Milton","Shakespeare","Austen"};
vector<const char*> articles={"a","an","the"};

list<string> list2(authors);//正确:数据类型以及容器类型都匹配
deque<string> authList(authors);//错误:容器类型不匹配
vector<string> words(arcticles);//错误:容器类型必须匹配
//正确:可以将const char*元素转换为string
forward_list<string> words(articles.begin(),articles.end());

//拷贝元素,直到it指向的元素
deque<string> authList(authors.begin(),it);

容器元素构造

1
2
3
4
vector<int> ivec(10,-1); //10个int元素,每个都初始化为-1
list<string> svec(10,"hi!"); //10个strings;每个都初始化为"hi!"
forward_list<int> ivec(10); //10个元素,每个都初始化为0
deque<string> svec(10); //10个元素,每个都是空string

容器赋值运算

1
2
3
4
5
6
7
8
9
10
11
c1=c2;//将c1中的元素替换为c2中元素的拷贝。c1和c2必须具有相同的类型

c={a,b,c.......};//基本初始化/拷贝方法

swap(c1,c2),c1.swap(c2);//交换c1和c2中的元素。c1和c2必须具有相同的类型。swap通常比从c2向c1拷贝元素快得多

seq.assign(b,e);//将seq中的元素替换为迭代器b和e所表示的范围中的元素。迭代器b和e不能指向seq中的元素

seq.assign(il);//将seq中的元素替换为初始化列表il中的元素

seq.assign(n,t);//将seq中的元素替换为n个值为t的元素

添加元素

1
2
3
4
5
6
7
8
//array不支持这些操作
//forward_list有自己专有版本的insert和emplace且不支持push_back和emplace_back
//vector和string不支持push_front和emplace—_front

c.push_back(t); //在c的尾部插入元素
c.emplace_back(arg)s;//在c的尾部创建由args创建的元素

c.push_front同理于back操作

访问元素

1
2
3
4
5
6
at和下标操作只适用于string,vector,deque和array
back不适用于forward_list
c.back();//返回c中尾元素的引用。若c为空,函数行为未定义
c.front();//返回c中首元素的引用。若c为空,函数行为未定义
c[n];//返回c中下标为n的元素的引用,n是一个无符号正数。若n>=c.size(),则函数行为未定义
c.at(n);//返回下标为n的元素的引用。如果下标越界,则抛出out_of_range的异常

访问成员函数返回的是引用

1
2
3
4
5
6
7
if(!c.empty()){
c.front()=42; //将42赋予c中的第一个元素
auto &v=c.back(); //获得指向最后一个元素的引用
v=1024; //改变c中的元素
auto v2=c.back(); //v2不是一个引用,它是c.back()的一个拷贝
v2=0; //未改变c中的元素
}

删除元素

1
2
3
c.pop_back();//删除c中尾元素,若c为空,函数行为未定义,函数返回void
c.erase();//删除迭代器p所指定的元素,返回一个指向被删元素之后的元素的迭代器,若p指向尾元素,则返回尾后(off-the-end)迭代器。若p是尾后迭代器,则函数行为未定义
c.clear();//删除c中所有元素

容器操作可能使迭代器失效

向容器添加元素后

  • 对于vector和string,且存储空间被重回新分配,则指向容器的迭代器、指针、引用都会失效。如果存储空间未重新分配,指向插入位置之前的元素的迭代器、指针和引用仍有效,但指向插入位置之后元素的迭代器、指针、引用都会失效
  • 对于deque,插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效。如果在首位位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效
  • list和forward_list总是有效

删除元素后,指向被删除元素的迭代器、指针、引用都会失效


由于向迭代器添加元素和从迭代器删除元素的代码可能会使迭代器失效,因此必须保证每次改变容器的操作之后都正确地重新定位迭代器。这个建议对vector,string和deque尤为重要

Vector

为了支持快速随机访问,vector将元素连续存储,vector通常会分配比新的空间需求更大的内存空间预留作为备用

vector管理容量的成员函数

1
2
3
4
5
shrink_to_fit//只适用于vector,string和deque
capacity和reserve//只适用于vector和string
c.shrink_to_fit();//请将capacity()减少为与size()相同大小
c.capacity();//不重新分配内存空间的话,c可以保存多少元素
c.reserve();//分配至少能容纳n个元素的内存空间,预留空间时可用

只有在执行insert操作时size与capacity相等,或者调用resize或reserve时给定的大小超过当前capacity,vector才可能重新分配内存空间。会分配多少超过给定容量的额外空间,取决于具体实现

string

构造string

1
2
3
string s(cp,n);//s是cp指向的数组中前n个字符的拷贝,此数组至少应该包含n个字符
string s(s2,pos2);//s是string s2从下标pos2开始的字符的拷贝。若pos2>s2.size(),构造函数的行为未定义
string s(s2,pos2,len2);//s是string s2从下标pos2开始len2个字符的拷贝。若pos2>s2.size(),函数行为未定义。不管len2的值是多少,构造函数至多拷贝s2.size()-pos2个字符

substr操作

1
2
3
4
5
string s("hello world");
string s2=s.substr(0,5);//s2=hello
string s3=s.substr(6);//s3=world
string s4=s.substr(6,11);//s3=world
string s5=s.substr(12);//抛出一个out_of_range异常

改变string的其他方法

1
2
3
4
5
6
7
8
9
10
s.insert(s.size(),5,'!');//在s末尾插入5个感叹号
s.erase(s.size()-5,5);//从s删除最后5个字符
const char*cp="Stately,plump Buck";
s.assign(cp,7);//s=="Stately"
s.insert(s.size(),cp+7);//s=="Stately,plump Buck"

string s="some string",s2="some other string";
s.insert(0,s2);//在s中位置0之前插入s2的拷贝
//在s[0]之前插入s2中s2[0]开始的s2.size()个字符
s.insert(0,s2,0,s2.size());

append和replace函数

1
2
3
4
5
6
7
8
string s("C++ Primer"),s2=s;//将s和s2初始化为"C++ Primer"
s.insert(s.size(),"4th Ed.");//s=="C++ Primer 4th Ed."
s2.append("4th Ed.");//s2=="C++ Primer 4th Ed."
//将"4th"替换为"5th"的等价方法
s.erase(11,3);//s=="C++ Primer Ed."
s.insert(11,"5th");//s=="C++ Primer 5th Ed."
//从位置11开始,删除3个字符并插入"5th"
s2.replace(11,3,"5th");//此例中调用replace时,插入的文本恰好与删除的文本一样长。这不是必须的,可以插入一个更长或更短的string

string搜索操作

1
2
3
4
5
6
7
8
9
10
11
12
13
string name("AnnaBelle");
auto pos1=name.find("Anna");//pos1==0
//这一段返回0,即子字符串"Anna"在"AnnaBelle"中第一次出现的下标

string lowercase("annabelle");
pos1=lowercase.find("Anna");//pos1==npos

string numbers("0123456789"),name("r2d2");//返回1即name中第一个数字的下标
auto pos=name.find_first_of(numbers);

string dept("03714p3");
//返回5--字符'p'的下标
auto pos=dept.find_first_not_of(numbers);

搜索

1
2
3
4
5
6
string::size_type pos=0;
//每步循环查找name中下一个数
while((pos=name.find_first_of(numbers,pos))!=string::npos){
cout<<"found number at index:"<<pos<<"element is"<<name[pos]<<endl;
++pos;//移动到下一个字符
}

compare函数

1
2
string s("kevin");
cout<<s.compare("kevin")<<endl;//输出为1

数值转换

1
2
3
int i=42;
string s=to_string(i);//将整数i转换为字符表示形式
double d=stod(S);//将字符串s转换为浮点数

容器适配器

除了顺序容器外,标准库还定义了三个顺序容器适配器:stack,quene和priorty_queue,适配器是标准库中的一个通用概念,一个适配器是一种机制,能使某种事物的行为看起来像一种不同的类型

1
2
3
4
5
6
7
8
9
10
11
//所有容器适配器都支持的操作和类型
size_type//一种类型
value_type//元素类型
container_type//实现适配器的底层容器类型
A a;//创建一个名为a的空适配器
A a(c);//创建一个带有c拷贝名为a的适配器
关系运算符
a.empty()//若a包含任何元素,返回false,否则返回true
a.size()//返回a中的元素数目
swap(a,b)//交换a和b的内容,a和b数据类型和容器类型必须相同
a.swap(b)//同上

定义一个适配器

1
2
3
4
5
6
//假设deq是一个deque<int>,我们可以用deq来初始化一个新的stack
stack<int> stk(deq);//从deq拷贝元素到stk
//在vector上实现的空栈
stack<string,vector<string>> str_stk;
//str_stk2在vector上实现,初始化时保存sevc的拷贝
stack<string,vector<string>> str_stk2(svec);

栈适配器

1
2
3
4
5
6
7
8
9
stack<int> intstack;//空栈
//填满栈
for(size_t ix=0;ix!=10;++ix)
intstack.push(ix);//intstack保存0到9十个数
while(!intstack.empty()){//intstack中有值就继续循环
int value=intstack.pop();
//使用栈顶值的代码
intstack.pop();//弹出栈顶元素,继续循环
}

IO


IO库类型和头文件

1
2
3
4
5
6
7
8
9
10
//头文件
iostream istream,wistream从流读取数据
ostream,wostream向流写入数据
iostream,wiostream读写流
fstream ifstream,wifsteam从文件读取数据
ofstream,wofstream向文件写入数据
fstream,wfstream读写文件
sstream istringstream,wistringstream从string读取数据
ostringstream,wistringstream向string写入数据
stringstream,wstringstream读写string

IO对象无拷贝或赋值

1
2
3
4
ofstream out,out2;
out1=ou2;//错误:不能对流对象赋值
ofstream print(ofstream);//错误:不能初始化ofstream参数
out2=print(out2);//错误:不能拷贝流对象

条件状态

IO操作一个与生俱来的问题就是可能发生错误。一些错误是可恢复的,而其他错误则发生在系统深处,已经超出了应用程序可以修正的范围。

1
2
3
4
5
strm::iostate  strm是一种IO类型
strm::badbit strm::babit用来指出流已崩溃
strm::failbit strm::failbit用来指出一个IO操作失败了
strm::eofbit strm::eofbit用来指出流到达了文件结束
strm::goodbit strm::goodbit用来指出流未处于错误状态。此值保证为零

一个流一旦发生错误,其上后续的IO操作都会失败。只有当一个流处于无错状态时,我们才可以从它读取数据,向它写入数据。由于流可能处于错误状态,因此代码通常应该在使用

IO类型之间的关系

​ 概念上设备类型和字符大小都不会影响我们要执行的IO操作。例如我们可以用>>读取数据,无论是从控制台窗口还是磁盘文件还是string读取。此种功能是由继承机制实现的。

确保一个流对象的状态

​ 将其当作一个条件来使用

1
2
while(cin>>word)
//OK:操作成功

泛型


定义

多线程


在了解线程之前,我们可以先了解一下线程、进程之间的区别以及联系

进程

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式和的描述,进程是程序的实体

线程

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务,线程是独立调度和分派的基本单位,线程可以为操作系统内核调度的内核线程,如win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用火狐进程,如windows7的线程,进行混合调度

一个进程可以有很多线程,每条线程并行执行不同的任务

异常