小编主页详情-请点击小编gitee代码仓库-请点击本文主要介绍了C基础(命名空间、C输入输出、缺省参数、函数重载、引用、inline 内联函数、nullptr)内容全由作者原创无AI并带有配图帮助博友们更好的理解点个关注不迷路下面进入正文~~目录1. 命名空间1.1 namespace的价值1.2 namespace的定义1.3 命名空间使用2. C输入输出3. 缺省参数4. 函数重载5. 引用5.1 引用的概念和定义5.2 引用的特性5.3 引用的使用5.4 const引用5.5 指针和引用的关系6. inline 内联函数7. nullptr结语1.命名空间1.1namespace的价值在C/C中变量、函数和后面要学到的类都是大量存在的这些变量、函数和类的名称将都存在于全局作用域中可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化以避免命名冲突或名字污染namespace关键字的出现就是针对这种问题的。c语言项目类似下面程序这样的命名冲突是普遍存在的问题C引入namespace就是为了更好的解决这样的问题#include stdio.h #include stdlib.h int rand 10; int main() { // 编译报错 error C2365 : “rand”: 重定义以前的定义是 “函数” printf(%d\n, rand); return 0; }1.2namespace的定义定义命名空间需要使用到namespace关键字后面跟命名空间的名字然后接一对{}即可{}中即为命名空间的成员。命名空间中可以定义变量/函数/类型等。namespace本质是定义出一个域这个域跟全局域各自独立不同的域可以定义同名变量所以下面的rand不再冲突了。C中域有函数局部域全局域命名空间域类域域影响的是编译时语法查找一个变量/函数/类型出处(声明或定义)的逻辑所有有了域隔离名字冲突就解决了。局部域和全局域除了会影响编译查找逻辑还会影响变量的生命周期命名空间域和类域不影响变量生命周期。命名空间只能定义在全局还可以嵌套定义。项目工程中多文件中定义的同名namespace会认为是一个namespace不会冲突。C标准库都放在一个叫std(standard)的命名空间中。#include stdio.h #include stdlib.h namespace LanYangYang { // 命名空间中可以定义变量/函数/类型 int rand 10; int Add(int left, int right) { return left right; } struct Node { struct Node* next; int val; }; } int a 0; int main() { // 这里默认访问的是全局的rand函数指针 printf(%p\n, rand); // 这里指定LanYangYang命名空间中的rand printf(%d\n, LanYangYang::rand); int a 1; printf(%d\n, a); // ::域作用限定符 // 如果就是想访问全局变量就可以不用指定命名空间 printf(%d\n,::a); return 0; }1.3命名空间使用编译查找一个变量的声明/定义时默认只会在局部或者全局查找不会到命名空间里面去查找。使用命名空间中定义的变量/函数有三种方式1. 指定命名空间访问项目中推荐这种方式。2. using将命名空间中某个成员展开项目中经常访问的不存在冲突的成员推荐这种方式。3. 展开命名空间中全部成员项目不推荐冲突风险很大日常小练习程序为了方便推荐使用。#includestdio.h namespace N { int a 0; int b 1; } int main() { // 编译报错error C2065“a”未声明的标识符 printf(%d\n, a); return 0; } // 指定命名空间访问 int main() { printf(%d\n, N::a); return 0; } // using将命名空间中某个成员展开 using N::b; int main() { printf(%d\n, N::a); printf(%d\n, b); return 0; } // 展开命名空间中全部成员 using namespace N; int main() { printf(%d\n, a); printf(%d\n, b); return 0; }2.C输入输出· iostream 是 Input Output Stream 的缩写是标准的输入、输出流库定义了标准的输入、输出对象。· std::cin 是 istream 类的对象它主要面向窄字节narrow characters (of type char)的标准输入流。· std::cout 是 ostream 类的对象它主要面向窄字节的标准输出流。· std::endl 是一个函数流插入输出时相当于插入一个换行字符加刷新缓冲区。· 是流插入运算符是流提取运算符。C语言还用这两个运算符做位运算左移/右移· 使用C输入输出更方便不需要像printf/scanf输入输出时那样需要手动指定格式C的输入输出可以自动识别变量类型本质是通过函数重载实现的其实最重要的是C的流能更好的支持自定义类型对象的输入输出。· IO流涉及类和对象运算符重载、继承等很多面向对象的知识这些知识我们还没有讲解所以这里我们只能简单认识一下C IO流的用法后面我们会有专门的一个章节来细节IO流库。· cout/cin/endl等都属于C标准库C标准库都放在一个叫std(standard)的命名空间中所以要通过命名空间的使用方式去用他们。· 一般日常练习中我们可以using namespace std实际项目开发中不建议using namespace std。· 这里我们没有包含stdio.h也可以使用printf和scanf在包含iostream间接包含了。vs系列编译器是这样的其他编译器可能会报错。#define _CRT_SECURE_NO_WARNINGS 1 #include iostream using namespace std; int main() { int a 0; double b 0.1; char c x; cout a b c endl; std::cout a b c std::endl; scanf(%d%lf, a, b); printf(%d %lf\n, a, b); // 可以自动识别变量的类型 cin a; cin b c; cout a endl; cout b c endl; return 0; } cpp #includeiostream using namespace std; int main() { // 在io需求比较高的地方如部分大量输入的竞赛题中加上以下3行代码 // 可以提高C IO效率 ios_base::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); return 0; }3.缺省参数缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时如果没有指定实参则采用该形参的缺省值否则使用指定的实参缺省参数分为全缺省和半缺省参数。有些地方把缺省参数也叫默认参数全缺省就是全部形参给缺省值半缺省就是部分形参给缺省值。C规定半缺省参数必须从右往左依次连续缺省不能间隔跳跃给缺省值。带缺省参数的函数调用C规定必须从左到右依次给实参不能跳跃给实参。函数声明和定义分离时缺省参数不能在函数声明和定义中同时出现规定必须函数声明给缺省值。#include iostream using 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); Func2(100); Func2(100, 200); Func2(100, 200, 300); return 0; }4.函数重载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; } // 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; } // 返回值不同不能作为重载条件因为调用时也无法区分 //void fxx() //{} //int fxx() //{ // return 0; //} // 下面两个函数构成重载 // 但是调用时会报错存在歧义编译器不知道调用谁 void f1() { cout f() endl; } void f1(int a 10) { cout f(int a) endl; } int main() { Add(10, 20); Add(10.1, 20.2); f(); f(10); f(10, a); f(a, 10); return 0; }5.引用5.1引用的概念和定义引用不是新定义一个变量而是给已存在变量取了一个别名编译器不会为引用变量开辟内存空间它和它引用的变量共用同一块内存空间。比如水壶传中李逵宋江叫“铁牛”江湖上人称“黑旋风”林冲外号豹子头类型 引用别名 引用对象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; }5.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; }5.3引用的使用引用在实践中主要是用于引用传参和引用做返回值中减少拷贝提高效率和改变引用对象时同时改变被引用对象。引用传参跟指针传参功能是类似的引用传参相对更方便一些。引用和指针在实践中相辅相成功能有重叠性但是各有特点互相不可替代。C的引用跟其他语言的引用(如Java)是有很大的区别的除了用法最大的点C引用定义后不能改变指向Java的引用可以改变指向。void Swap(int rx, int ry) { int tmp rx; rx ry; ry tmp; } int main() { int x 0, y 1; cout x y endl; swap(x, y); cout x y endl; return 0; } #includeiostream #includeassert.h using namespace std; typedef int STDataType; typedef struct Stack { STDataType* a; int top; int capacity; } ST; 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(rs.a); // 注意原代码此处为 assert(ps); 应为 assert(rs.a) 或类似这里按原文保留风格 // 满了扩容 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; } // int STTop(ST rs) int STTop(ST rs) { assert(rs.top 0); return rs.a[rs.top - 1]; } int main() { // 调用全局的 ST st1; STInit(st1); STPush(st1, 1); STPush(st1, 2); cout STTop(st1) endl; STTop(st1) 10; cout STTop(st1) endl; return 0; }5.4const引用· 可以引用一个const对象但是必须用const引用。const引用也可以引用普通对象因为对象的访问权限在引用过程中可以缩小但是不能放大。· 需要注意类似 int rb a3; double d 12.34; int rd d; 这样一些场景下a3的结果保存在一个临时对象中int rd d 也是类似在类型转换中会产生临时对象存储中间值也就是rb和rd引用的都是临时对象而C规定临时对象具有常性所以这里就触发了权限放大必须要用常引用才可以。· 所谓临时对象就是编译器需要一个空间暂存表达式的求值结果时临时创建的一个未命名的对象C中把这个未命名对象叫做临时对象。int main() { const int a 10; // 编译报错: error C2440: “初始化”: 无法从“const int”转换为“int ” // 这里的引用是对a访问权限的放大 // int ra a; // 这样才可以 const int ra a; // 编译报错: error C3892: “ra”: 不能给常量赋值 // ra; // 这里的引用是对b访问权限的缩小 int b 20; const int rb b; // 编译报错: error C3892: “rb”: 不能给常量赋值 // rb; return 0; } #includeiostream using namespace std; int main() { int a 10; const int ra 30; // 编译报错: 初始化: 无法从int转换为int //int rb a * 3; const int rb a * 3; double d 12.34; // 编译报错: 初始化: 无法从double转换为int // int rd d; const int rd d; return 0; }5.5指针和引用的关系C中指针和引用就像两个性格迥异的亲兄弟指针是哥哥引用是弟弟在实践中他们相辅相成功能有重叠性但是各有自己的特点互相不可替代。· 语法概念上引用是一个变量的取别名不开空间指针是存储一个变量地址要开空间。· 引用在定义时必须初始化指针建议初始化但是语法上不是必须的。· 引用在初始化时引用一个对象后就不能再引用其他对象而指针可以不断地改变指向对象。· 引用可以直接访问指向对象指针需要解引用才是访问指向对象。· sizeof中含义不同引用结果为引用类型的大小但指针始终是地址空间所占字节个数(32位平台下占4个字节64位下是8byte)· 指针很容易出现空指针和野指针的问题引用很少出现引用使用起来相对更安全一些。6.inline 内联函数· 用inline修饰的函数叫做内联函数编译时C编译器会在调用的地方展开内联函数这样调用内联函数就不需要建立栈帧了就可以提高效率。· inline对于编译器而言只是一个建议也就是说你加了inline编译器也可以选择在调用的地方不展开不同编译器关于inline什么情况展开各不相同因为C标准没有规定这个。inline适用于频繁调用的短小函数对于递归函数代码相对多一些的函数加上inline也会被编译器忽略。· C语言实现宏函数也会在预处理时替换展开但是宏函数实现很复杂很容易出错的且不方便调试C设计了inline目的就是替代C的宏函数。· vs编译器debug版本下面默认是不展开inline的这样方便调试debug版本想展开需要设置一下以下两个地方。· inline不建议声明和定义分离到两个文件分离会导致链接错误。因为inline被展开就没有函数地址链接时会出现报错。#includeiostream using namespace std; // 实现一个ADD宏函数的常见问题 // #define ADD(int a, int b) return a b; // #define ADD(a, b) a b; // #define ADD(a, b) (a b) // 正确的宏实现 #define ADD(a, b) ((a) (b)) // 为什么不能加分号? // 为什么要加外面的括号? // 为什么要加里面的括号? int main() { int ret ADD(1, 2); cout ADD(1, 2) endl; cout ADD(1, 2) * 5 endl; int x 1, y 2; ADD(x y, x | y); // - (xyx/y) return 0; } #include iostream using namespace std; inline void f(int i) { cout i endl; } int main() { f(10); return 0; }7.nullptrNULL实际是一个宏在传统的C头文件(stddef.h)中可以看到如下代码#ifndef NULL #ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0) #endif #endif· C中NULL可能被定义为字面常量0或者C中被定义为无类型指针(void)的常量。不论采取何种定义在使用空值的指针时都不可避免的会遇到一些麻烦本想通过f(NULL)调用指针版本的f(int)函数但是由于NULL被定义成0调用了f(int x)因此与程序的初衷相悖。f((void*)NULL);调用会报错。· C11中引入nullptrnullptr是一个特殊的关键字nullptr是一种特殊类型的字面量它可以转换成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题因为nullptr只能被隐式地转换为指针类型而不能被转换为整数类型。#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(int x)因此与程序的初衷相悖。 f(NULL); f((int*)NULL); // 编译报错error C2665“f”2个重载中没有一个可以转换所有参数类型 // f((void*)NULL); f(nullptr); return 0; }结语这篇文章全文由作者手写图片由画图软件所制无AI制作希望各位博友能有所收获欢迎各位博友的讨论觉得不错的小伙伴别忘了点赞关注哦~