北京网站建设价,网站是别人做的 ftp账号吗,有代码怎么做网站,wordpress qq登录后页面跳转文章目录 前言一、C历史及发展1.C是什么2.C历史 二、开始C1.基础类型1.第一个简单的C程序2.命名空间1.命名空间的介绍2.命名空间的使用3.命名空间的using声明与using指示 3.初识输入输出操作4.引用1.引用概念2.引用的使用1.引用做参数2.引用做返回值 3.引用和指针的区别4.const… 文章目录 前言一、C历史及发展1.C是什么2.C历史 二、开始C1.基础类型1.第一个简单的C程序2.命名空间1.命名空间的介绍2.命名空间的使用3.命名空间的using声明与using指示 3.初识输入输出操作4.引用1.引用概念2.引用的使用1.引用做参数2.引用做返回值 3.引用和指针的区别4.const的引用 5.auto关键字1.auto简介2.复合类型常量和auto1.auto与指针和引用结合使用 2.语句1.范围for 3.函数1.缺省参数的函数1.缺省参数的概念2.缺省参数的分类1.全缺省参数2.半缺省参数 2.函数的重载1.函数重载的概念2.调用重载的函数 3.内敛函数 4.其他1.nullptr 前言
C是在C的基础之上容纳进去了面向对象编程思想并且增加了许多有用的库。熟悉C语言对C学习有很大的帮助C是对C语言的补充和对C语言进行优化。本章我们学习一点C的基础内容先浅浅的了解一下C。
一、C历史及发展
了解一门语言我们要先看一下它的发展历史和出现的原因方便我们更好的了解语言适合解决什么样的问题。
1.C是什么
在20世纪80年代 计算机界提出了OOP(object oriented programming面向对象)思想为了解决C语言对于复杂的问题规模较大的程序需要高度的抽象和建模时的困境。 在1982年Bjarne Stroustrup本贾尼博士在C语言的基础上引入并扩充了面向对象的概念发明了一种新的语言。为了表达该语言与C语言的关系命名为C。因此C是基于C语言而产生的它既可以进行C语言的过程化程序设计又可以进行以抽象数据类型为特点的基于对象的程序设计还可以进行面向对象的程序设计。
2.C历史
1979年贝尔实验室的Bjarne Stroustrup本贾尼等人试图分析unix内核的时候试图将内核模块化于是在C语言的基础上进行扩展增加了类的机制完成了一个可以运行的预处理程序称之为C with classes。现在公司主流使用还是C98和C11。
二、开始C
1.基础类型
1.第一个简单的C程序
#includeiostream
using namespace std;
int main()
{int a 0;int b 0;//从键盘上获得值相当于C语言的scanf函数cin a b;//把结果输出到屏幕相当于C语言的printf函数cout a b endl;return 0;
}相信大部分课本的代码案例都是如此。让我们看一下运行的结果吧. 这个简单的小程序就实现了但是在CPrimer中是下面的写法
#includeiostream
int main()
{int a 0;int b 0;std::cin a b;std::cout a b std::endl;return 0;
}结果和上面一模一样那么上面的using namespace std和std::分别是什么呢为什么可以有两种写法呢
2.命名空间
针对上面的问题我们来介绍一下C中的命名空间。
1.命名空间的介绍
因为变量、函数和类的名称将都存在于全局作用域中可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化以避免命名冲突或名字污染namespace关键字的出现就是针对这种问题的。 对比上面的代码我们可以看出使用了未加using namespace std在实现代码中多了一些东西这是因为我们用到的库函数基本都属于命名空间std例如std::cin表示标准输入中获取内容。此处使用的是作用域操作符::意思是编译器应该从操作符左侧的作用域来寻找右侧的名字。因此std::cin的意思是使用命名空间std的名字cin
2.命名空间的使用
当我们多个文件使用的全局变量函数或者结构体使用的名字相同时这时间就会产生命名冲突在C语言中是没半法很好的解决这些问题的。如 那么在C中如何解决这些问题的呢我们引入了命名空间这个概念。 namespace关键字后面跟命名空间的名字然后加一对{}{}中即为命名空间的成员。 加入我们的命名空间就可以解决这样的问题了命名空间也是可以嵌套的。
3.命名空间的using声明与using指示
我们先看什么是using声明与using指示 区别 一条using声明语句一次只引入命名空间的一个成员它使得我们很清楚地知道程序中所用的到底是哪个名字。一条using声明语句可以出现在全局作用域局部作用域命名空间作用域以及类的作用域在类的作用域中这样的声明语句只能指向基类成员。 using指示中我们无法控制哪些名字是可见的因为所有名字都是可见的。且using指示不可以出现在类中。 在using指示中以关键字using开始后面跟namespace关键字以及命名空间的名字如果这里的名字不是已经定义好的命名空间名字程序将会发生错误。 命名空间只会影响使用不会影响生命周期。 更加详细的可以看CPrimer。
3.初识输入输出操作
C并未定义任何输入输出(IO)语句而是包含了一个全面的标准库来提供IO机制。我们输入输出使用了iostream库iostream库包含两个基础类型istream和ostream分别表示输入流和输出流 标准库定义了4个IO对象为了处理输入我们使用了一个cin的istream类型的对象这个对象也被称为标准输入。对于输出我们使用了一个cou的ostream类型的对象此对象也被称为标准输出。标准库还定义了其他两个ostream对象分别为cerr和clog我们一般用cerr来输出警告和错误信息因此cerr也叫标准错误。而clog用来输出程序运行时的一般性信息。
#includeiostream
using namespace std;
int main()
{int a 0;int b 0;cin a b;//标准输入cout a b endl;//标准输出return 0;
}上面一个简单的相加程序就用到了们标准输入和标准输出。 (输出运算符)运算符接受两个运算对象左侧的运算对象必须是一个ostream对象右侧的对象是要打印的值。此运算符将给定的值写入到给定的ostream对象中。 (输入运算符)与输出运算符相似它接受一个istream作为其左侧运算对象接收一个对象作为右侧运算对象。此运算符将给定的值写入到给定的ostream对象中。 cin a;cin b;cin a b;//效果和上面等价cout a ;cout b ;cout a b;//效果和上面等价4.引用
1.引用概念
引用不是新定义一个变量而是给已存在变量取了一个别名编译器不会为引用变量开辟内存空间它和它引用的变量共用同一块内存空间。
int main()
{//类型 引用变量名(对象名) 引用实体int a 10;int sa a;//sa指向a(是a的另一个名字)int saa;//报错引用必须被初始话return 0;
}定义引用时程序把引用和它的初始值绑定在一起而不是将初始值拷贝给引用一旦初始话完成引用将和它的初始值对象一直绑定在一起。因为无法更改绑定对象所以引用必须初始化
int main()
{int a 10;int sa a;a;//对a进行cout sa sa endl;//sa的值也会随着发生改变sa;//对sa进行cout a a endl;//a的值也会随着发生改变return 0;
}下面我们看一些引用的例子
int main()
{int val 10;int rval1 val;//把val的值赋给rval1int rval2 val;//rval2是val的引用int rval3 rval2;//rval3是rval2的引用此时也是val的引用int rval4 10;//错误引用类型的初始值必须是一个对象double rval5 val;//错误此处引用的初始值类型必须为double类型return 0;
}在引用中引用只能绑定在对象中而不能与字面值或某个表达式的计算结果绑定到一起。但也有例外情况。原因也会在下面演示。
2.引用的使用
1.引用做参数
void Swap(int* a, int* b)//指针类型接收参数
{int tmp *a;*a *b;*b tmp;
}
void SWAP(int sa, int sb)//引用类型接收参数此时的sa就是main函数中的asb是main函数中的b
{int tmp sa;sa sb;sb tmp;
}
int main()
{int a 10;int b 20;Swap(a, b);//指针类型的传参printf(a %d b %d\n, a, b);SWAP(a, b);//引用类型传参printf(a %d b %d\n, a, b);return 0;
}2.引用做返回值
int ADD(int a, int b)
{static int c 0;c a b;return c;
}
int add(int a, int b)
{int c 0;c a b;return c;
}
int main()
{int a 10;int b 20;int c1 ADD(a, b);cout c1 endl;cout ADD(a, b) val is: c1 endl;int c2 add(a, b);cout c2 endl;cout add(a, b) val is: c2 endl;return 0;
}上面的代码有什么结果呢 对比可以发现当引用做返回值时如果函数返回时出了函数作用域如果返回对象还在(还没还给系统)则可以使用引用返回如果已经还给系统了则必须使用传值返回。因为函数调用创建栈帧会使用该片空间。
3.引用和指针的区别
int main()
{int val 10;int vbl 20;int rval val;//rval是val的引用int* pa val;//pa是指向val的指针(*pa);cout val endl;rval;cout val endl;pa vbl;//pa现在指向vblreturn 0;
}运行结果 观察汇编代码 我们发现引用的汇编指令和指针的汇编指令相同引用的底层逻辑就是指针。 引用和指针的区别
引用概念上定义一个变量的别名指针存储一个变量地址引用在定义时必须初始化引用不可以为NULL指针可以为NULL引用初始化后就不可以更改而指针随时更改指向对象。在sizeof中引用结果为引用类型的大小但指针始终是地址空间所占字节个数。引用自加即引用的实体增加1指针自加即指针向后偏移一个类型的大小。有多级指针但是没有多级引用。访问实体方式不同指针需要显式解引用引用编译器自己处理引用比指针使用起来相对更安全。
4.const的引用
我们可以把引用绑定到const对象上就像绑定到其他的对象上一样我们称之为对常量的引用与普通引用不同的是对常量的的引用不能被作用修改它所绑定的对象。
int main()
{const int a 10;const int ra1 a;//引用及其对象都是常量int ra1 a;//错误试图让一个非常量引用指向一个常量对象
}在我们上面看到的引用类型必须与其所引用的对象类型一致但有两个例外 一是在初始化常量引用时允许任意表达式作为初始值只要该表达式的结果可以转化为引用类型即可尤其允许为一个常量引用绑定非常量的对象字面值甚至是一个表达式。
int main()
{int a 10;const int sa1 a;//允许将const int 绑定到一个普通int对象上const int sa2 10;//sa2是常量引用因为sa2不可以改变所以可以绑定到常量上const int sa3 sa1 * 2;//sa3是常量引用int sa4 sa1 * 2;//sa4是一个普通引用不可以绑定到常量上return 0;
}我们下面来看由duoble绑定int类型为什么不可以
int main()
{double val 3.14;const int sval1 val;//正确int sval2 val;//错误return 0;
}我们来分析一下原因 类型转化都会产生临时变量临时变量具有常属性所以这也是为什么上面的绑定不成功的原因我们需要用常引用来进行绑定。
5.auto关键字
1.auto简介
在早期C/C中auto的含义是使用auto修饰的变量是具有自动存储器的局部变量但遗憾的是一直没有人去使用它于是C11中标准委员会赋予了auto全新的含义即auto不再是一个存储类型指示符而是作为一个新的类型指示符来指示编译器auto声明的变量必须由编译器在编译时期推导而得。显然auto定义的变量必须有初始值。
int Testauto()
{return 0;
}
int main()
{int a 10;auto b a;auto c a;auto d Testauto();cout typeid(b).name() endl;cout typeid(c).name() endl;cout typeid(d).name() endl;return 0;
}2.复合类型常量和auto
编译器推断出来的auto类型有时间和初始值的类型并不完全一样编译器会适当的改变结果类型使其更符合初始化规则
1.auto与指针和引用结合使用
使用引用其实就是使用引用的对象特别是当引用被用作初始值时真正参与初始化的其实是引用对象的值
int main()
{int a 10;int b a;auto c b;cout typeid(c).name() endl;return 0;
}int main()
{int x 10;auto a x;auto* b x;auto c x;cout typeid(a).name() endl;cout typeid(b).name() endl;cout typeid(c).name() endl;return 0;
}从上面我们可以看出用auto声明指针类型时用auto和auto*没有任何区别但用auto声明引用类型时则必须加 要在一条语句中定义多个变量*和只从属于某个声明符而非基本数据类型的一部分初始值必须是同一类型。
int main()
{auto a 1, b 2;auto c 1, d 2.0;// 该行代码会编译失败因为c和d的初始化表达式类型不同return 0;
}注意:auto不能作为函数的参数, auto也不可直接声明数组。
2.语句
1.范围for
对于一个有范围的集合而言由程序员来说明循环的范围是多余的有时候还会容易犯错误。因 此C11中引入了基于范围的for循环。for循环后的括号由冒号“ ”分为两部分第一部分是范围内用于迭代的变量第二部分则表示被迭代的范围。
//语法形式
for (declaration : expression)
{statement;
}expression表示的必须是一个序列如数组vector或者string类型等类型的对象这些类型的共同特点就是拥有能返回迭代器的begin和end成员后面会提迭代器 declaration定义一个变量序列中的每个元素都可以转换为该变量的类型。确保这些类型的最简单办法就是使用auto。 statement循环语句。
int main()
{int tmp[] { 1,2,3,4,5,6,7,8,9 };for (auto i : tmp){cout i ;}return 0;
}这就是一个简单范围for 思考一下下面的代码中数组的内容会不会改变呢
int main()
{int tmp[] { 1,2,3,4,5,6,7,8,9 };for (auto i : tmp){i * 2;cout i ;}cout endl;for (auto i : tmp){cout i ;}return 0;
}对比发现我们对定义变量的改变并不会影响我们数组的内容。因为我们对定义的变量是对序列中元素的拷贝。对变量的改动并不会影响我们序列中的元素。 我们再来看下面的代码
int main()
{int tmp[] { 1,2,3,4,5,6,7,8,9 };for (auto i : tmp){i * 2;cout i ;}cout endl;for (auto i : tmp){cout i ;}return 0;
}此时我们对变量的更改影响了我们数组中的元素因为我们第一次范围for中的定义的变量为引用它是序列元素的别名对这个变量的改变就相当于对数组内容的改变。 判断下面的代码是否是正确的呢
void Testfor(int tmp[])
{for (auto i : tmp){cout i ;}
}
int main()
{int tmp[] { 1,2,3,4,5,6,7,8,9 };return 0;
}答案是不正确的范围for循环迭代的范围必须是确定的对于数组而言就是数组中第一个元素和最后一个元素的范围对于类而言应该提供begin和end的方法begin和end就是for循环迭代的范围。 我们看到编译器也允许这样使用在上面代码中数组传参退化为了指针此时用范围for找不begin和end的位置。
3.函数
1.缺省参数的函数
1.缺省参数的概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时如果没有指定实参则采用该形参的缺省值否则使用指定的实参。 如
void Cout(int a 0)
{cout a endl;
}
int main()
{int a 10;Cout();Cout(a);return 0;
}当我们没有像这个函数传入参数时函数使用的默认值0当我们传入参数时函数使用我们所传递的参数。
2.缺省参数的分类
1.全缺省参数
void Cout(int a 1, int b 2, int c 3)
{couta aendl;coutb bendl;coutc cendl;cout endl;
}上面这个就是全缺省参数的函数所有的形参都有默认值。 下面我们来测试一下函数
int main()
{int a 10;int b 20;int c 30;Cout();Cout(a);Cout(a,b);Cout(a,b,c);Cout(c);return 0;
}我们可以发现当我们传入的参数小于所需的个数时形参会从左向右依次接收。
2.半缺省参数
void Cout(int a, int b 2, int c 3)
{couta aendl;coutb bendl;coutc cendl;cout endl;
}此时这个函数就是半缺省参数。 注意半缺省参数必须从右往左依次来给出不能间隔着给且缺省参数不能在函数声明和定义中同时出现。一旦某个形参被赋予了默认值那么他后面的所有形参都必须有默认值。
2.函数的重载
C允许在同一作用域中声明几个功能类似的同名函数这些同名函数的形参列表(参数个数 或 类型 或 类顺序)不同。常用来处理实现功能类似数据类型不同的问题。
1.函数重载的概念
在同一作用域内的几个函数名字完全相同但形参列表不同我们称之为重载函数
int Add(int a, int b)
{return a b;
}
double Add(double a, double b)
{return a b;
}
int Add(int a, double b)
{return a b;
}
int main()
{int a 10, b 20;double c 10.125, d 20.25;cout Add(a, b) endl;cout Add(c, d) endl;return 0;
}如上述代码中的Add就构成了重载 构成函数的重载必须是函数参数不相同如个数类型等
2.调用重载的函数
当我们定义了一组重载函数后我们需要合理的实参来调用他们函数匹配是一个过程函数匹配也叫重载确定用编译器决定调用哪一个函数。 此时有三种情况 1.编译器找到一个与实参最佳辟匹配的函数并生成调用该函数。 2.找不到任何一个函数与调用的实参相匹配此时编译器发出无匹配的错误信息。 3.有多于一个函数可以进行匹配但每个都不是最佳选择此时也发生错误称为二义性调用。 如上图出自《程序员的自我修养》p89是vs对函数重载在编译器的名称。
3.内敛函数
我们可以将函数指定为内敛函数inline,编译时C编译器会在调用内联函数的地方展开没有函数调用建立栈帧的开销内联函数提升程序运行的效率。
inline int Add(int a, int b)
{return a b;
}
int main()
{int a 10, b 20;int c Add(a, b);return 0;
}上面在编译过程展开类似下面的形式
int c a b;//直接对函数进行展开内敛说明只是向编译器发送一个请求编译器可以忽略这个请求。 一般来说内敛机制用于优化规模小流程直接调用频繁的函数很多编译器都不支持内敛递归函数。 nline是一种以空间换时间的做法如果编译器将函数当成内联函数处理在编译阶段会用函数体替换函数调用缺陷可能会使目标文件变大优势少了调用开销提高程序运行效率。
4.其他
1.nullptr
nullptr是一种特殊类型的字面值。 在过去的程序中用的名为NULL的预处理变量来给指针赋值这个变量在头文件cstdilb中定义它的值为0因此使用NULL初始化指针和用0初始化指针是一样的。在C中最好使用nullptr同时尽量避免使用NULL。
void f(int)
{cout f(int) endl;
}
void f(int*)
{cout f(int*) endl;
}
int main()
{f(0);f(NULL);return 0;
}我们可以发现当我们传NULL时并没有走指针的函数所以当我们传指针类型时避免使用NULL