从C语言到C++:C++入门1
从C语言到CC入门1本系列旨在以C语言为基础介绍C的基本语法和特性。C语言中的基本语法包括变量、函数、循环、条件语句等在C中同样适用。C在C语言的基础上新增了一些特性如类、对象、继承、多态等。C相较于C语言最大的优势在于其对面向对象编程的支持这使得C更符合人类的思维模式更符合实际问题的解决需求。下面我们将以C语言开发中会遇到的具体问题为导向逐步介绍C的其他特性。C对于C语言的兼容C对于C语言的兼容性是其最大的优势之一。C可以无缝地与C语言代码混合使用这使得C在开发中可以利用C语言的成熟库和工具同时也可以利用C的面向对象编程优势。C中使用C语言的语法输出Hello World!#includestdio.hintmain(){printf(Hello World!\n);return0;}C中使用C的语法输出Hello World!#includeiostreamusingnamespacestd;intmain(){coutHello World!endl;return0;}上面两个代码都是简单的C程序它们都会打印出Hello World!。对于一些已经熟悉C语言的开发者来说C的开发会更加方便因为C可以无缝地与C语言代码混合使用同时也可以利用C的面向对象编程优势。因此我们在这里不在详细介绍C的基础语法而是直接进入C的其他特性。namespace命名空间命名空间的定义在使用C语言开发时我们通常会遇到一些问题如变量名冲突、函数名冲突等。#includestdio.h#includestdlib.hintrand10;intmain(){// 编译报错error C2365: “rand”: 重定义以前的定义是“函数”printf(%d\n,rand);return0;}命名冲突案例1上面的代码中我们定义了一个变量rand但是rand函数在stdlib.h中已经存在同时他们都存在于全局域中导致编译报错。#includestdio.hintx10;intmain(){intx20;printf(%d\n,x);return0;}命名冲突案例2上面的代码中main函数的局部域中存在一个变量x而全局域中也存在一个变量x局部域的x会隐藏全局域的x最终打印出20。案例1或许在日常开发中可以通过修改变量名来解决大型项目不同大型项目不同模块之间的变量名冲突会更加严重导致代码维护困难特别是在有多个团队共同开发的情况下每个团队可能会定义相同的变量名导致代码冲突。而案例2可能会导致代码的不可预测性因为局部域的x会隐藏全局域的x导致在main函数中打印出20而不是10。为了解决这些问题C引入了命名空间的概念。命名空间是一个特殊的区域用于存储变量、函数、类等。每个命名空间都有一个唯一的名称用于区分不同的命名空间。命名空间的语法如下namespacenamespace_name{// 命名空间中的代码}命名空间的使用命名空间的代码可以包含变量、函数、结构体、以及今后会学习到的类等。namespaceexm-zem{// 变量intrand10;// 函数intAdd(intleft,intright){returnleftright;}// 结构体structNode{structNode*next;intdata;};}命名空间的代码可以嵌套在其他命名空间中。namespaceexm-zem{intrand10;intAdd(intleft,intright){returnleftright;}structNode{structNode*next;intdata;};// namespace嵌套定义namespaceC{intrand1;}namespaceCPP{intrand2;}}使用命名空间中的变量、函数、结构体等时需要使用**双冒号::**来访问。在嵌套命名空间中需要使用多个双冒号::来访问。#includestdio.h#includestdlib.hnamespaceexm-zem{intrand10;intAdd(intleft,intright){returnleftright;}structNode{structNode*next;intdata;};// namespace嵌套定义namespaceC{intrand1;}namespaceCPP{intrand2;}}intmain(){printf(%d\n,exm-zem::rand);printf(%d\n,exm-zem::C::rand);printf(%d\n,exm-zem::CPP::rand);printf(%d\n,exm-zem::Add(1,2));return0;}输出10 1 2 3上面的代码中我们定义了一个命名空间exm-zem在其中定义了一个变量rand一个函数Add一个结构体Node以及两个嵌套的命名空间C和CPP。而在main函数中我们使用双冒号::来访问exm-zem命名空间中的变量、函数、结构体等。这样我们在使用rand变量时既不会和stdlib.h中的rand函数冲突exm-zem和其中嵌套的C、CPP命名空间中的rand变量之间也不会冲突。命名空间的作用域C中有4中域全局域局部域类域命名空间域双冒号就是用于指定所使用的域。当未指定域时如未使用双冒号::时与C语言相同默认从局部域开始查找如果未找到再从全局域开始查找不会进入命名空间域进行查找。当双冒号左侧置空时表示从全局域开始查找。#includestdio.hintx10;intmain(){intx20;printf(%d\n,x);printf(%d\n,::x);return0;}输出20 10命名空间的生命周期在C语言中对于一个变量、函数、结构体等它的生命周期取决于它的作用域在C中也是如此。对于命名空间来说它只能在全局域中定义因此它的生命周期与全局域相同。我们可以认为命名空间是一个全局域的单独的子域用于存储变量、函数、结构体等。多文件命名空间在多文件模块化编程中每个文件都可以定义自己的命名空间互不干扰。而同名的命名空间会被认为是同一个命名空间不会冲突。// file1.cpp#includestdio.hnamespaceexm-zem{intrand110;}// file2.cpp#includestdio.hnamespaceexm-zem{intrand220;}命名空间的展开在C中当我们进行日常小练习或者进行算法竞赛时很少会出现命名空间冲突的情况因此我们会直接展开命名空间中的代码而不是使用双冒号::来访问。#includeiostream// 标准输入输出流今后会学习到usingnamespacestd;intmain(){coutHello World!endl;// cout是标准输出流endl是换行符用于在输出后换行是插入运算符用于将数据插入到输入流中return0;}在这段代码中我们引入了很多新的概念如标准输入输出流、endl、等。这些概念在今后的学习中会继续介绍。std是标准命名空间用于存储标准库中的函数、结构体等。所有C提供的的标准库函数、结构体等都被定义在std中避免了命名空间冲突。当然如果你并不想把命名空间中的所有内容都展开你可以只展开你需要的内容。#includeiostreamusingstd::cout;intmain(){coutHello World!std::endl;return0;}只展开cout而不展开endl初探IO流在C语言中我们使用printf和scanf来实现输入输出。在C中我们使用cout和cin来实现输入输出。#includeiostreamusingstd::cout;usingstd::cin;usingstd::endl;intmain(){intx;cinx;coutxendl;return0;}输入10输出10相较于C语言中的printf和scanfcout和cin的使用更加方便也更加安全。cout和cin是标准输入输出流而printf和scanf是格式化输入输出函数它们需要手动处理格式化问题而cout和cin则不需要手动处理格式化问题。cout和cin可以自动识别输入输出的数据类型这也为之后我们将要学习到的类类型提供了更方便的输入输出操作。引入cout和cin的本质目的就是为了支持任意类型的输入输出操作。#includeiostream#includestringusingnamespacestd;intmain(){string s;cins;coutsendl;return0;}输入Hello World!输出Hello World!这里我们用到了string类用于存储字符串之后我们也会详细介绍。cin和cout可以识别并输出字符串而printf和scanf则不能直接进行这样的操作。缺省参数在C中我们可以为函数的参数设置默认值当调用函数时如果没有提供该参数的值就会使用默认值这也被称为缺省参数。#includeiostreamusingstd::cout;usingstd::cin;usingstd::endl;voidFunc(intx10){coutxendl;}intmain(){Func();Func(20);return0;}输出10 20使用缺省参数时需要注意缺省参数必须是常量表达式不能是变量。缺省参数的顺序必须与函数的参数顺序一致。缺省参数可以是函数的任意参数包括非缺省参数。设定缺省参数时必须从右向左连续设定不能从左向右设定。传入参数时必须从左向右连续传入不能从右向左传入。对于这上面的1-3条还是比较容易理解的而4-5条我们来详细解析一下如果我们不遵守第四条#includeiostreamusingnamespacestd;voidFunc(intx10,inty){coutxendl;coutyendl;}intmain(){Func(30);return0;}那么传入的30究竟是传给x还是y显然这会引起歧义因此我们规定设定缺省参数时必须从右向左连续设定不能从左向右设定。同样假设我们不遵守第五条#includeiostreamusingnamespacestd;voidFunc(intx10,inty20,intz30){coutxendl;coutyendl;}intmain(){Func(,30,400);Func(30,,400);return0;}这同样可能会引起歧义30和400究竟是传给x、y还是z因此我们规定传入参数时必须从左向右连续传入不能从右向左传入。正确设定和使用缺省参数的方式#includeiostreamusingnamespacestd;voidFunc(intx10,inty20,intz30){coutxendl;coutyendl;coutzendl;}intmain(){Func1();Func1(1);Func1(1,2);Func1(1,2,3);return0;}在进行C语言使用栈时我们可能会遇到这两种情况已经确定了栈的大小。不确定栈的大小。// Stack.h#includeassert.h#includestdlib.htypedefintSTDataType;typedefstructStack{STDataType*a;inttop;intcapacity;}ST;voidSTInit(ST*ps,intn);voidSTPush(ST*ps,intx);// Stack.cpp#includeStack.hvoidSTInit(ST*ps,intn){assert(psn0);ps-a(STDataType*)malloc(4*sizeof(STDataType));// ... 扩容ps-top0;ps-capacityn;}voidSTPush(ST*ps,intx){//.. 此处省去具体代码}// main.cpp#includeStack.h#includeiostream#includeStack.husingnamespacestd;intmain(){// 已经确定了有100项ST st1;STInit(st1);for(size_t i0;i100;i){STPush(st1,i);}// 不确定有多少项ST st2;STInit(st2);STPush(st2,1);STPush(st2,2);STPush(st2,3);return0;}当我们无法确定具体大小时此时只能依靠扩容来满足需求。而当我们已经可以确定具体大小时在C语言当中还是只能默认初始化4个或指定的类型的大小然后反复扩容导致资源重复消耗。C引入了缺省参数我们可以为函数的参数设置默认值当调用函数时如果没有提供该参数的值就会使用默认值这也被称为缺省参数。这样我们既可以满足不确定有多少项的默认初始化4个类型大小也可以满足这里的已经有确定大小而直接进行指定大小初始化的情况。另外在声明与定义函数分离时缺省参数必须要在声明时指定不能在定义时指定。需要缺省参数时通过声明可以直接将缺省参数的值传递给函数而不需要在调用时传递。引入缺省参数后修改的程序// Stack.h#includeassert.h#includestdlib.htypedefintSTDataType;typedefstructStack{STDataType*a;inttop;intcapacity;}ST;voidSTInit(ST*ps,intn4);// 可以使用缺省参数满足两种情况voidSTPush(ST*ps,intx);// Stack.cpp#includeStack.hvoidSTInit(ST*ps,intn){assert(psn0);ps-a(STDataType*)malloc(n*sizeof(STDataType));// 这里可以根据n来初始化栈的大小// ...ps-top0;ps-capacityn;}voidSTPush(ST*ps,intx){//..}// main.cpp#includeiostream#includeStack.husingnamespacestd;intmain(){// 已经确定了有100项ST st1;STInit(st1,100);// 已经确定了栈的大小在这里可以传入具体大小for(size_t i0;i100;i){STPush(st1,i);}// 不确定有多少项ST st2;STInit(st2);// 不确定栈的大小在这里可以使用缺省参数4STPush(st2,1);STPush(st2,2);STPush(st2,3);return0;}本章完