网站含义,湖北什么是网络营销,岳阳公司做网站,外贸网站推广策划C之从C过渡 前言
暂时告别C语言#xff0c;我们走进C。对于有C语言基础#xff0c;初学C的我们来说#xff0c;在正式学习C的主体内容之前#xff0c;我们需要先有一个过渡#xff0c;本文中会总结过渡需要了解的零散知识#xff0c;主要是语法。 正文
C的第一个程序
…C之从C过渡 前言
暂时告别C语言我们走进C。对于有C语言基础初学C的我们来说在正式学习C的主体内容之前我们需要先有一个过渡本文中会总结过渡需要了解的零散知识主要是语法。 正文
C的第一个程序
C兼容C语⾔绝⼤多数的语法所以C语⾔实现的helloworld依旧可以运⾏C中需要把定义⽂件 代码后缀改为.cppvs编译器看到是.cpp就会调⽤C编译器编译linux下要⽤g编译不再是gcc。
// test.cpp
#includestdio.hint main()
{printf(hello world\n);return 0;
}然而C有⼀套⾃⼰的输⼊输出严格说C版本的helloworld应该是这样写的。
// test.cpp
// 这⾥的std cout等看不懂下面会依次讲解
#includeiostream
using namespace std;
int main()
{cout hello world\n endl;return 0;
}
命名空间
这个东西的出现是为了解决C语言中的一些问题。比如 这是一段C语言代码问题出在我们包含的stdlib.h这个头文件我们知道头文件在预处理阶段会展开也就是会将头文件拷贝过来而在stdlib.h里有rand函数的定义所以就与我们定义的变量命名冲突了。
也有可能是我们在与其他人协作时代码合并后出现了很多冲突。可以说冲突就是C语言的一大问题。
所以我们在C中有命名空间。
namespace的定义
定义命名空间需要使⽤到namespace关键字后面跟命名空间的名字然后接⼀对{}即可{}中即为命名空间的成员。命名空间中可以定义变量/函数/类型等。namespace本质是定义出⼀个域这个域跟全局域各自独立不同的域可以定义同名变量所以下面的rand不在冲突了。C中域有函数局部域全局域命名空间域类域域影响的是编译时语法查找⼀个变量/函数/ 类型出处(声明或定义)的逻辑所有有了域隔离名字冲突就解决了。局部域和全局域除了会影响编译查找逻辑还会影响变量的生命周期命名空间域和类域不影响变量生命周期。
#includestdio.h
#includestdlib.h//域
namespace oct
{//命名空间中可以定义变量、函数和类型int rand 10;
}//注意这里是没有分号的int main()
{//编译报错重定义printf(%d\n, rand);//此时rand已经被封起来了这里的rand访问的是函数rand//这里如果真的想打印rand改为%p更好return 0;
}可以看出上面namespace定义中所说的这个域跟全局域各自独立。
就近原则
int a 0;
int main()
{int a 1;printf(%d\n, a);return 0;
}在这段代码中全局和局部都有个a那么我们打印时访问的是哪个a呢答案是局部的这是由于就近原则先在局部找再去全局找。
那如果我们现在就想要访问全局的这个a该怎么办呢
这时候我们就需要用到**“域作用限定符”**也就是两个冒号。 冒号左边不给东西就默认去全局域查找了。
那现在我们想要访问namespace里的变量怎么做呢
在域作用限定符左侧给上命名空间的名字也就是告诉编译器直接去这个命名空间中查找。 查找不是运行时而是编译时原则上C/C编译的时候用一个函数或者变量都得找到它的声明或者定义。关于生命周期namespace不影响生命周期namespace中定义的变量也是全局变量。只不过是访问时只有指定才能找到的全局变量。我们不能在局部定义域在main里面写namespace。
在命名空间中定义函数和结构
namespace oct
{//命名空间中可以定义变量、函数和类型int rand 10;int Add(int left, int right){return left right;}struct Node{struct Node* next;int val;};
}注意在命名空间中定义结构我们要访问时的使用方式是这样的
struct oct::Node p1;namespace的定义
namespace的前三条定义我们就看完了现在再看三个定义
namespace只能定义在全局当然他还可以嵌套定义。项⽬⼯程中多⽂件中定义的同名namespace会认为是⼀个namespace不会冲突。C标准库都放在⼀个叫std(standard)的命名空间中。
命名空间的嵌套定义
在一个公司中我们可能有多个项目组不同组用不同的命名空间就不会冲突了。但是对于在同一个项目组的不同人来说各自在各自的电脑上写然后要合代码比如用git提交代码合起来可能就冲突了。也就是说在一个命名空间内部的冲突。
namespace oct
{namespace october{int rand 1;int Add(int left, int right){return left right;}}namespace octopus{int rand 2;int Add(int left, int right){return (left right)*10;}}
}当然还可以再往下嵌套。
那么嵌套的命名空间里的成员如何访问 多文件同名namespace不会冲突
同一个命名空间我们可以在同一个文件中定义多份也可以在多个文件中定义多份。
比如我们现在有栈、队列、链表的文件将它们都封装在namespace oct中逻辑上会将它们合并而不是冲突。否则我们一个文件一个命名空间得定义很多的命名空间显然不合适。
比如我们现在在全局定义一个静态的栈再在命名空间定义动态的栈在使用时我们就可以通过域的隔离分别使用不会冲突。
C标准库都放在⼀个叫std(standard)的命名空间中
标准库也怕和别人冲突。
C标准库也有很多个各自的文件都用namespace std合并封起来。
命名空间的使用 命名空间的使用有三种方式。 编译查找⼀个变量的声明/定义时默认只会在局部或者全局查找不会到命名空间里面去查找。所以下面程序会编译报错。所以我们要使⽤命名空间中定义的变量/函数有三种⽅式
指定命名空间访问项⽬中推荐这种⽅式。using将命名空间中某个成员展开项⽬中经常访问的不存在冲突的成员推荐这种⽅式。展开命名空间中全部成员项⽬不推荐冲突⻛险很⼤⽇常⼩练习程序为了⽅便推荐使⽤。
上面我们使用的方式是指定命名空间访问。
而每次都指定命名空间还是挺麻烦的。 全展开
现在可以看到我们就可以不用指定直接使用命名空间里的a和b了。而这种写法是命名空间全展开。
但是展开整个命名空间的风险是很大的。只适合我们平时写日常小练习程序。
某个成员展开
所以我们还有一种折中的方式把命名空间中的某个成员展开。
假如我们要频繁使用a且a不存在冲突风险 所以一般在项目中我们选择将前两种方法结合使用。
注意在展开头文件和展开命名空间中都提到“展开”但二者并非同一个意思。展开头文件指的是将这个头文件内容拷贝过来。而展开命名空间相当于我们将用于隔离的墙拆除了。
C输⼊输出
是Input Output Stream的缩写是标准的输⼊、输出流库定义了标准的输⼊、输出对象。std::cin是istream类的对象它主要面向窄字符narrow characters(of type char)的标准输⼊流。std::cout是ostream类的对象它主要面向窄字符的标准输出流。std::endl是⼀个函数流插⼊输出时相当于插⼊⼀个换⾏字符加刷新缓冲区。
只有内存里才有整型、浮点型这样的概念在其他的地方比如文件、网络都是字符流的概念。
整型有原码、反码、补码浮点数也有自己的存储规则这都是内存里的存储。内存中有这样的概念是为了方便运算。
不管我们用cout还是printf整型其实都是转换成字符。比如1234被转换为4个字符然后再输出。-1234被转换成5个字符再输出。任何类型都要转换为字符再输出到控制台。
是流插⼊运算符是流提取运算符。C语⾔还⽤这两个运算符做位运算左移/右移
#includeiostream
using namespace std;//一般选择展开更方便int main()
{int i 1234;int j -1234;cout i endl;}使⽤C输⼊输出更⽅便不需要像printf/scanf输⼊输出时那样需要⼿动指定格式C的输⼊ 输出可以自动识别变量类型(本质是通过函数重载实现的这个以后会讲到)其实最重要的是 C的流能更好的⽀持⾃定义类型对象的输⼊输出。
我们的插入可以一次写很多个连一起。
#includeiostream
using namespace std;int main()
{int a 0;double b 0.1;char c x;cout a b c endl;return 0;
}还可以这样写
cout a b c \n;在C中用\n也可以换行。
而这样写就不行
cout a ;cout也就是流插入其实是函数调用只能有一个参数。是二元操作符只能有两个操作数。
可以看到C语言需要指定类型比较麻烦而C却可以自动识别。
我们再来试试输入流提取。
从控制台中提取然后转化成对应的类型。 可以看到可以一次输入多个。
endl比较复杂是一个函数。它的底层不是一个换行符。其实cout和cin也很复杂。这些都需要日后学得更深入再具体了解。 IO流涉及类和对象运算符重载、继承等很多面向对象的知识这些知识我们日后讲解。 cout/cin/endl等都属于C标准库C标准库都放在⼀个叫std(standard)的命名空间中所以要通过命名空间的使⽤⽅式去⽤他们。 这⾥我们没有包含stdio.h也可以使⽤printf和scanf在包含间接包含了。vs系列编译器是这样的其他编译器可能会报错。
小细节
关于C语言中的控制精度 可以看到我们的C是默认保留小数点后5位。也可以人为控制但是不建议了解。在我们想要控制时可以用回printf。
在用scanf时我们需要取地址因为scanf是个函数[形参的改变不会影响实参所以我们要取地址。]而在C中我们就无需取地址。
再次总结io流函数的定义在iostream头文件里的std命名空间里可以自动识别类型可以一行多个输入输出。
补充
#includeiostreamusing namespace std;int main()
{// 在io需求⽐较⾼的地⽅如部分⼤量输⼊的竞赛题中加上以下3⾏代码 // 可以提⾼CIO效率 ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);return 0;
}
C的IO流由于各种各样的原因比如要兼容C语言而这种兼容会付出一些代价。IO输入输出是带缓冲区的各自有各自的缓冲区要兼容就得去关注对应的缓冲区。为了提高性能除了换用printf、scanf还可以选择把这三句代码加上。
缺省参数
• 缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调用该函数时如果没有指定实参则采⽤该形参的缺省值否则使用指定的实参缺省参数分为全****缺省和半****缺省参数。有些地方把缺省参数也叫默认参数
• 全缺省就是全部形参给缺省值半缺省就是部分形参给缺省值。C规定半缺省参数必须从右往左依次连续缺省不能间隔跳跃给缺省值。
• 带缺省参数的函数调⽤C规定必须从左到右依次给实参不能跳跃给实参。
• 函数声明和定义分离时缺省参数不能在函数声明和定义中同时出现规定必须函数声明给缺省值。
#include iostreamusing namespace std;void Func(int a 0)//缺省参数
{cout a endl;
}int main()
{Func(); // 没有传参时使⽤参数的默认值 Func(10); // 传参时使⽤指定的实参 return 0;
}其实就是如果有传就用传的没有就用默认的值缺省值。
#include iostream
using namespace std;// 全缺省
void Func1(int a 10, int b 20, int c 30)
{cout a a endl;cout b b endl;cout c c endl endl;
}// 半缺省
void Func2(int a, int b 10, int c 20)
{cout a a endl;cout b b endl;cout c c endl endl;
}int main()
{Func1();//一个都不传Func1(1);//传一个Func1(1,2)//传两个Func1(1,2,3);//全传Fun1(,2,);//这样传不可以Func2();//不能一个都不传Func2(100);//传一个Func2(100, 200);//传两个Func2(100, 200, 300);//全传return 0;
}
注意半缺省参数必须从右往左依次连续缺省不能间隔跳跃给缺省值。
缺省参数在实践中的意义
之前我们在写栈的插入数据时如果后续要插入很多数据我们就要多次扩容效率低下而如果初始化时一把开好就可以避免扩容。我们可以使用缺省参数
(test.cpp中)
#includeiostream
using namespace std;#includeStack.hint main()
{// 确定知道要插入1000个数据初始化时一把开好避免扩容moon::ST s2;moon::STInit(s2, 1000);for (size_t i 0; i 1000; i){moon::STPush(s2, i);}return 0;
}(stack.h中)
void STInit(ST* ps, int n 4);(stack.cpp中) 这样我们就一把开好不需要扩容效率就得到了提高。在我们不知道多大时不传参数默认就为缺省参数。
缺省参数是很好用的日后就知道了。
函数重载
C⽀持在同⼀作用域中出现同名函数但是要求这些同名函数的形参不同可以是参数个数不同或者类型不同。这样C函数调用就表现出了多态行为使用更灵活。C语言是不⽀持同⼀作⽤域中出现同名函数的。
这个东西非常有用。可以说是C语言的一大缺陷。
#includeiostream
using namespace std;
// 1、参数类型不同
int Add(int left, int right)
{cout int Add(int left, int right) endl;return left right;
}double Add(double left, double right)
{cout double Add(double left, double right) endl;return left right;
}int main()
{Add(1,2);Add(1.1,2.2);return 0;
}以上代码C语言是无法做到的。这种情况能同名显然是更好的。
再如
void Swap(int* px,int* py)
{}void Swap(double* px, double* py)
{}交换函数如果能够同名显然是非常好。
// 2、参数个数不同
void f()
{cout f() endl;
}void f(int a)
{cout f(int a) endl;
}// 3、参数类型顺序不同(本质也是类型不同)
void f(int a, char b)
{cout f(int a,char b) endl;
}void f(char b, int a)
{cout f(char b, int a) endl;
}坑
// 下⾯两个函数构成重载因为参数个数不同
// f()但是调用时会报错存在歧义编译器不知道调⽤谁 void f1()
{cout f() endl;
}void f1(int a 10)
{cout f(int a) endl;
}
//返回值不同不能作为重载条件因为调⽤时也⽆法区分
void fxx()
{}int fxx()
{return 0;
}引用
这也是C中非常好用的一个东西。
引用的概念和定义
引⽤不是新定义⼀个变量⽽是给已存在变量取了⼀个别名编译器不会为引⽤变量开辟内存空间 它和它引⽤的变量共⽤同⼀块内存空间。⽐如⽔壶传中李逵宋江叫铁⽜江湖上⼈称黑旋风林冲外号豹⼦头 类型引用别名引用对象; C中为了避免引⼊太多的运算符会复⽤C语⾔的⼀些符号⽐如前面的和这⾥引⽤也和取地址使⽤了同⼀个符号从使⽤⽅法⻆度区分就可以。
#includeiostream
using namespace std;int main()
{int a 0;// 引⽤b和c是a的别名 int b a;int c a;// 也可以给别名b取别名d相当于还是a的别名 int d b;d;// 这⾥取地址我们看到是⼀样的 cout a endl;cout b endl;cout c endl;cout d endl;return 0;
}无论叫a b c还是d都是同一块空间的名字。给其中一个名字加1其他的名字的值也都加1。 引用的实践
以前我们要交换两个变量的值得使用传地址的方式。 我们现在使用引用就无需传地址也能交换两个变量的值。
理解这个我们不要去想底层底层也是指针在语法层面就是取别名。
也就是rx是x的别名ry是y的别名。改变别名就能改变本身。这样并没有为形参开辟新的空间。
我们可能会见到以下的代码
void STInit(ST rs, int n 4)
{rs.a (STDataType*)malloc(n * sizeof(STDataType));rs.top 0;rs.capacity n;
}这里就用别名来替换了指针。
再或者这样
// 栈顶
void STPush(ST rs, STDataType x)
{//assert(ps);// 满了 扩容if (rs.top rs.capacity){printf(扩容\n);int newcapacity rs.capacity 0 ? 4 : rs.capacity * 2;STDataType* tmp (STDataType*)realloc(rs.a, newcapacity *sizeof(STDataType));if (tmp NULL){perror(realloc fail);return;}rs.a tmp;rs.capacity newcapacity;}rs.a[rs.top] x;rs.top;
}有了引用之后进入函数我们就不需要断言了。
还有原本下面的这个函数我们是要用二级指针作为参数的因为可能链表为空push的这个就要成为头结点那么plist就会改变而如果参数为一级指针phead改变无法改变plist于是我们用二级指针。
void ListPushBack(LTNode* phead, int x)
{PNode newnode (PNode)malloc(sizeof(LTNode));newnode-val x;newnode-next NULL;if (phead NULL){phead newnode;}else{//...}
}int main()
{LTNode* plist NULL;ListPushBack(plist,1);return 0;
}
而现在我们可以给指针取别名就不用二级指针。所以我们现在改变指针phead就是改变main中的指针plist。
typedef struct ListNode
{int val;struct ListNode* next;
}LTNode, *PNode;//等价于
typedef struct ListNode* PNode;PNode是指向LTNode类型的指针结构体指针。
所以上面函数也可以写成
void ListPushBack(PNode phead, int x)本文到此结束但过渡内容没有结束敬请期待下文_