一.第一个C程序和C语言一样刚开始让我们尝试打印一下“hello world“//test.cpp #includeiostream using namespace std; int main() { cout hello world endl; return 0; }里面的运行逻辑不用纠结下面会一一展开详细讲解。二.命名空间1.namespace的价值在C和C的学习和使用中我们会接触到大量的变量函数和后面要学习的类因为这些库内的函数和类都存在于全局域之中很可能与我们自己定义的变量函数或类发生命名冲突。为了避免命名冲突和名字污染namespace被定义出来解决着这个问题。我们先来看一下C语言常见的命名冲突问题通过它也能更好的理解namespace存在的意义和价值。#includestdio.h #includestdlib.h int rand 10; int main() { //编译报错rand重定义以前的定义是函数 printf(%d\n, rand); return 0; }我们定义的rand变量和stdlib.h头文件中的函数重名了编译报错。2.namespace的定义①定义命名空间要使用namespace关键字后面跟上空间的名字然后加一对{}即可{}之中的即为命名空间的成员。其中可以定义变量函数类型等。注意不用像定义结构体那样在后面加 struct list { int size; }; namespace wxxh { int a; }②namespace的本质是定义出一个域这个域和全局域各自独立在不同的域里可以定义同名变量。③C中有局部域全局域命名空间域类域域影响的是编译时语法查找一个变量/函数/类型的逻辑默认只会先在局部域找后在全局域找。不会主动进入命名空间域和类域查找所以有了域隔离名字冲突就解决了。注全局域和局部域会影响生命周期而类域和命名空间域不会。④namespace只能定义在全局当然它也可以嵌套定义。namespace wxxh { int a; namespace wxxh1 { char a; } namespace wxxh2 { double a; } }就像namespace虽然定义在全局但是访问时默认不会进入一样在访问外部的命名空间时也默认不会进入内部嵌套的命名空间。⑤ :: 域作用限定符#includestdio.h int bit 10; namespace lgf { int bit 20; } int main() { int bit 30; printf(%d\n, bit); printf(%d\n,::bit); printf(%d\n, lgf::bit); return 0; }在这里定义了三个bit变量如果我不使用域作用限定符 :: 那会优先在局部里查找先打印30然后 ::bit在::前面不见任何东西就是默认在全局域里查找打印10最后使用 lgf::bit在lgf命名空间里查找打印20⑥项目工程中多文件中定义的同名namespace会认为时一个namespace不会冲突。//main.cpp namespace lgf { int a 1; int b 2; } //test.h namespace lgf { extern int a; extern int b; } //test.cpp #includetest.h using namespace std; #includeiostream namespace lgf { int c a b; } int main() { cout lgf::c endl; return 0; }ab都定义在main.cpp里而test.cpp能通过他俩去定义c⑦c标准库都放在一个叫std(standard)的命名空间中。理解std是一个大命名空间里面装着所有标准库的东西而头文件(不带.h的)是这个大命名空间中允许你去访问的范围二者要配合使用。如你想使用cout就既要使用iostream头文件又要使用std::去找到它或者展开std命名空间。3.命名空间的使用编译查找一个变量的声明或定义时默认只会在局部和全局查找不会到命名空间里面去查找。所以我们想使用命名空间中定义的变量或函数有下面三种方式• 指定命名空间访问安全系数高项目中推荐这种方式。//在每个要使用的对象前都加std:: std::cout i std::endl;• using将命名空间中的某个成员展开适合展开项目中经常访问的且不存在冲突的成员推荐该方式#includeiostream using std::cout; using std::endl; int main() { int i; std::cini; cout i endl; return 0; }• 展开命名空间中的全部成员项目不推荐冲突风险大日常练习方便推荐使用。#includeiostream using namespace std; int main() { int i; cin i; cout i endl; return 0; }三.C输入输出• iostream 是Input Output Stream的缩写是标准的输入、输出库定义了标准的输入输出对象• std::cin 是istream类(类型)的对象(变量)它主要面向窄字符(narrow characters)的标准输入流。• std::cout 是ostream类(类型)的对象(变量)它主要面向窄字符的标准输出流。• std::endl 是操作符底层由函数实现使用流插入运算符输出时相当于插入一个换行字符(\n) 刷新缓冲区。#includeiostream using namespace std; int main() { cout hello \n; cout hello endl; return 0; }这两者在输出的表现上是一样的但是endl多了一个刷新缓冲区的功能。• 是流插入运算符 是流提取运算符。(在c语言中这两个运算符还做位运算的左移/右移)。• 使用C的输入输出更加方便因为不用像printf和scanf那样去指定类型C的输入输出可自 动识别变量类型(本质是通过函数重载实现的这个在后面会讲)更重要的是C的流能更好的支 持自定义类型对象的输入输出。• cin/cout/endl 都属于C标准库而C标准库都放在std命名空间中所以要像上面命名空间里讲的那样去使用他们。• 在VS里没有包含stdio.h也能使用printf是因为在包含iostream的时候间接包了其他编译器可能会报错。四.缺省参数• 缺省参数是声明或定义函数时为函数指定一个缺省值。在调用函数时如果没有指定实参则采用该形参的缺省值否则使用实参的指定值缺省参数分为全缺省和半缺省。(有些地方也把缺省参数叫做默认参数)。#includeiostream using namespace std; void print(int i -1) { cout i endl; } int main() { print(); print(50); return 0; }输出结果• 全缺省就是全部的形参都给缺省值半缺省就是部分形参给缺省值。C规定半缺省参数必须从右往左依次连续缺省不能间隔跳跃给缺省值。#includeiostream using namespace std; void fun(int a 1, int b, int c 3) { cout a b c endl; } int main() { //编译报错缺少形参2的默认实参因为我没从右往左给缺省值实参(1,2)不知道到底对应谁 fun(10, 20); return 0; }• 带缺省参数的函数调用C规定必须从左往右依次给实参不能跳跃给实参。如以下代码void fun(int a 1, int b 2, int c 3) { cout a b c endl; } int main() { //编译报错语法错误 fun(10, , 30); return 0; }当然和正常函数一样参数还是从左往右依次对应的。• 函数声明和定义分离时缺省参数不能在函数声明和定义中同时出现规定必须在函数声明处给缺省值。因为怕你两个地方给的缺省值不一样在声明出给方便查看调整。int add(int a 0, int b 0); int add(int a, int b) { return a b; }五.函数重载C支持在同一作用域中支持同名函数但是要求这些同名函数的形参不同可以是参数个数不同也可以是类型不同。这样C函数就表现出了多态行为使用更加灵活。C语言是不支持的。①个数不同#includeiostream using namespace std; int add(int a, int b, int c) { return a b c; } int add(int a, int b) { return a b; } int main() { int a 1, b 2, c 3; cout add(a, b) endl add(a,b,c) endl; return 0; }输出 3 和 6②类型不同#includeiostream using namespace std; double add(double a, double b) { return a b; } int add(int a, int b) { return a b; } int main() { int a 1, b 2; double c 1.5, d 3.14; cout add(a, b) endl add(c, d) endl; return 0; }输出 3 和 4.64③顺序不同这个本质还是类型不同void fun(int a, char c); void fun(char c, int a);注意返回值不同不能作为函数重载的条件因为返回值并不刚需就算写了返回值也比一定接收。④以下两个函数构成函数重载但编译器会报错存在歧义编译器不知道用哪个#includeiostream using namespace std; void fun() { cout fun() endl; } void fun(int a 0) { cout fun(int a) endl; } int main() { fun(1); //编译报错“fun”: 对重载函数的调用不明确 fun(); return 0; }因为如果你不传参数理论上两个fun都能接收导致函数调用发生歧义。结论函数的全缺省和无参函数不能同时存在。六.引用1.引用的概念和定义引用不是去定义一个变量而是给一个已经存在的变量去取一个别名编译器不会为引用变量开辟内存空间(语法角度),它和它的引用对象共占一块内存空间。比如林冲外号“豹子头”。写法类型 引用别名 引用对象;int a 0; int b a;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; }2.引用的特性• 引用在定义时必须初始化• 一个变量可以有多个引用• 引用一旦引用一个实体再不能引用其他实体#includeiostream using namespace std; int main() { int a 10; // 编译报错“ra”: 必须初始化引用 int ra; int b a; int c 20; // 这里并非让b引用c因为C引用不能改变指向 // 这里是一个赋值 b c; cout a endl; cout b endl; cout c endl; return 0; }3.引用的使用• 引用在实践中主要用于引用传参和引用返回在这两个过程中可达到减少拷贝提高效率和改变引用对象时同时改变被引用的对象的效果。• 引用传参跟指针传参的效果是一样的引用传参相对更方便一点。• 传值返回会产生临时变量作为函数调用表达式的返回值因为临时变量具有常性(就像是被const修饰一样)不可改变所以在一些想要修改返回值的场景中并不好用。传引用返回本质是返回返回对象的引用(别名)从而提高了效率(没有了拷贝这一步)同时可以改变返回对象。不是所有场景都可以传引用返回如果是一个返回值是当前函数的局部对象传引用返回是有风险的这个我们后面类和对象再深入讲解。• 引用做形参-可以在外部函数中改变实参引用做返回值-可以在主函数中改变外部变量。4.const引用• 可以引用一个const对象但是必须使用const引用。const引用也可以引用普通对象因为对象的访问权限在引用的过程中可以平移、缩小但不能放大。int main() { //编译报错因为权限不能被放大你变量a本身都不能被修改但是你给了个别名后就能通过别名修改了这不合理 const int a 10; int ra a; //权限的平移 const int b 20; const int rb b; //权限的缩小 int c 30; const int rc c; return 0; }• 注意以下两种情况也会产生临时变量①表达式的存储结果//编译报错 int ra a*3; //编译通过 const int ra a*3;表达式的存储结果是一个临时变量想要用引用接收就必须用const修饰的引用也是因为权限不能放大。②类型转换int main() { double d 3.14; //编译报错 int rd d; //编译通过 const int rd d; return 0; }为什么加上const后就能编译通过了呢这是因为在类型转换的过程中是先把d转换成一个int类型的临时变量再传给rd因临时变量具有常性加上const后就能正常运行了• 所谓临时对象就是编译器需要一个空间去暂存表达式的求值结果时创建的一个未命名的对象也就是临时对象。目前学到的会产生临时变量的有①传值返回②类型转换③表达式存储结果• 指针也一样面临着权限的放大缩小的问题。int main() { const int a 1; int b 2; int* p1 a;//编译报错权限不能放大 const int* p2 a;//权限平移 const int* p3 b;//权限缩小 return 0; }5.指针和引用的关系C中指针和引用就像两个性格迥异的亲兄弟指针是哥哥引用是弟弟在实践中他们相辅相成功能有重叠性但是各有自己的特点互相不可替代。• 语法概念上引用是一个变量的取别名不开空间指针是存储一个变量地址要开空间。注意在底层引用就是拿指针来实现的本质也是传地址• 引用在定义时必须初始化指针建议初始化但是语法上不是必须的。• 引用在初始化时引用一个对象后就不能再引用其他对象而指针可以在不断地改变指向对象。• 引用可以直接访问指向对象指针需要解引用才是访问指向对象。• sizeof中含义不同引用结果为引用类型的大小但指针始终是地址空间所占字节个数(32位平台下占4个字节64位下是8字节)• 指针很容易出现空指针和野指针的问题引用很少出现(传引用返回一个临时变量时也会出现)引用使用起来相对更安全一些。七.inline•用inline关键字修饰的函数叫做内联函数编译时C编译器会在调用的地方展开内联函数这样调用内敛函数就不用建立栈帧了就可以提高效率。• inline对于编译器来说只是一个建议也就是说你加了inline编译器也可以选择在调用的地方不展开。inline适合频繁调用的短小函数对于递归函数代码相对多一些的函数加上inline也会被编译器忽略。• C语言实现宏函数也会在预处理时替换展开但是宏函数实现很复杂很容易出错的且不方便调试C设计了inline目的就是替代C的宏函数。• inline不建议声明和定义分离到两个文件分离会导致链接错误。因为inline被展开就没有函数地址链接时会出现报错。就直接一起写到.h的头文件里就好了。• vs编译器 debug版本下面默认是不展开inline的这样方便调试debug版本想展开需要设置一下以下两个地方。①先右击项目名选择属性②进行设置八.nullptr• C11中引入nullptr是为了替换NULLnullptr是一个特殊的关键字nullptr是一种特殊类型的字面量它的值为0但它可以转换成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题因为nullptr只能被隐式地转换为指针类型而不能被转换为整数类型。• NULL实际是一个宏在传统的C头文件中可以看到以下代码#ifndef NULL #ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0) #endif #endif因其在一些地方会被直接替换成0所以会产生一些问题#includeiostream using namespace std; void f(int x) { cout f(int x) endl; } void f(int* ptr) { cout f(int* ptr) endl; } int main() { f(0); // 本想通过f(NULL)调用指针版本的f(int*)函数但是由于NULL被定义成0调用了f(intx)因此与程序的初衷相悖。 f(NULL); f((int*)NULL); // 编译报错error C2665: “f”: 没有与参数列表匹配的重载函数f的实例 //f((void*)NULL); f(nullptr); return 0; }输出结果完。