尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

C++-----1.基础杂知识

C++-----1.基础杂知识 一、命名空间1.1 命名空间定义在C/C中变量、函数和后面要学到的类都是大量存在的这些变量、函数和类的名称将都存在于全局作用域中可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化以避免命名冲突或名字污染namespace关键字的出现就是针对这种问题的。// bit是命名空间的名字一般开发中是用项目名字做命名空间名。 // 1. 正常的命名空间定义 namespace bit { // 命名空间中可以定义变量/函数/类型 int rand 10; int Add(int left, int right) { return left right; } 注意一个命名空间就定义了一个新的作用域命名空间中的所有内容都局限于该命名空间中 2.2 命名空间使用 命名空间中成员该如何使用呢比如 struct Node { struct Node* next; int val; }; } //2. 命名空间可以嵌套 // test.cpp namespace N1 { int a; int b; int Add(int left, int right) { return left right; } namespace N2 { int c; int d; int Sub(int left, int right) { return left - right; } } } //3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。 // ps一个工程中的test.h和上面test.cpp中两个N1会被合并成一个 // test.h namespace N1 { int Mul(int left, int right) { return left * right; } }注意一个命名空间就定义了一个新的作用域命名空间中的所有内容都局限于该命名空间中1.2 命名空间使用namespace bit { // 命名空间中可以定义变量/函数/类型 int a 0; int b 1; int Add(int left, int right) { return left right; 命名空间的使用有三种方式 加命名空间名称及作用域限定符 使用using将命名空间中某个成员引入 使用using namespace 命名空间名称 引入 } struct Node { struct Node* next; int val; }; } int main() { // 编译报错error C2065: “a”: 未声明的标识符 printf(%d\n, a); return 0; }命名空间的使用有三种方式1加命名空间名称及作用域限定符int main() { printf(%d\n, N::a); return 0; }2使用using将命名空间中某个成员引入using N::b; int main() { printf(%d\n, N::a); printf(%d\n, b); return 0; }3使用using namespace 命名空间名称 引入using namespce N; int main() { printf(%d\n, N::a); printf(%d\n, b); Add(10, 20); return 0; }二、C输入输出#includeiostream // std是C标准库的命名空间名C将标准库的定义实现都放到这个命名空间中 using namespace std; int main() { coutHello world!!!endl; return 0; }说明1. 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时必须包含 iostream 头文件以及按命名空间使用方法使用std。2. cout和cin是全局的流对象endl是特殊的C符号表示换行输出他们都包含在包含 iostream 头文件中。3. 是流插入运算符是流提取运算符。4. 使用C输入输出更方便不需要像printf/scanf输入输出时那样需要手动控制格式。C的输入输出可以自动识别变量类型。5. 实际上cout和cin分别是ostream和istream类型的对象和也涉及运算符重载等知识这些知识我们我们后续才会学习。std命名空间的使用惯例std是C标准库的命名空间如何展开std使用更合理呢1. 在日常练习中建议直接using namespace std即可这样就很方便。2. using namespace std展开标准库就全部暴露出来了如果我们定义跟库重名的类型/对象/函数就存在冲突问题。该问题在日常练习中很少出现但是项目开发中代码较多、规模大就很容易出现。所以建议在项目开发中使用像std::cout这样使用时指定命名空间 using std::cout展开常用的库对象/类型等方式。三、函数参数3.1 缺省参数概念缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时如果没有指定实参则采用该形参的缺省值否则使用指定的实参。void Func(int a 0) { coutaendl; } int main() { Func(); // 没有传参时使用参数的默认值 Func(10); // 传参时使用指定的实参 return 0; }3.2 缺省参数分类半缺省参数必须从右往左依次来给出不能间隔着给缺省参数不能在函数声明和定义中同时出现如果函数声明赋值是a,b为10函数实现是赋值a,b为20编译会报错3.3 占位参数C中函数的形参列表里可以有占位参数用来做占位调用函数时必须填补该位置。四、函数重载自然语言中一个词可以有多重含义人们可以通过上下文来判断该词真实的含义即该词被重载了。比如以前有一个笑话国有两个体育项目大家根本不用看也不用担心。一个是乒乓球一个是男足。前者是“谁也赢不了”后者是“谁也赢不了”4.1 函数重载概念函数重载是函数的一种特殊情况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; } int main() { Add(10, 20); Add(10.1, 20.2); f(); f(10); f(10, a); f(a, 10); return 0; }只是返回值不同不能构成重载上述图片中执行func(10)函数执行的是第2个void func(const int a)int a 10func(a)函数执行第一个函数重载的实现原理C语言不支持重载的原因C语言在符号表中直 接使用原函数名作为标识当出现同名函数时无法区分不同实现版本。C支持重载的机制采用函数名修饰规则在符号表中使用包含参数类型信息的修饰名。例如int add(int,int) 修饰为 _Z3addiivoid func(int,double*,int) 修饰为 _Z4funciPdi关键区别C通过将参数类型信息编码到函数名中使得同名不同参数的函数在符号表中具有不同标识。五、 引用5.1 引用的定义及使用引用不是新定义一个变量而是给已存在变量取了一个别名编译器不会为引用变量开辟内存空间它和它引用的变量共用同一块内存空间。比如蔡徐坤江湖上人称IKUN大家也叫他“坤哥“。语法形式类型后加符号区别于取地址操作符如int ra a;物理意义引用与被引用变量共享同一块内存空间例如定义int a 10;后int ra a;表示ra和a指向同一内存单元可继续定义int b ra;此时a、ra、b都指向同一空间操作特性通过任一别名修改值会影响所有引用如b 10;会使a、ra的值都变为10查看地址时会发现所有引用的地址值相同void TestRef() { int a 10; int ra a;//定义引用类型----ra是a的引用----ra是a的别名 printf(%p\n, a); printf(%p\n, ra); }tips引用类型必须和引用实体是同种类型的引用必须要初始化int main() { int a10; int b20; //int c; //错误引用必须要初始化 int ca; //一旦初始化后就不可以更改 cb //这是赋值操作不是更改引用 return 0; }5.2 引用的特性a引用在定义时必须初始化b一个变量可以有多个引用c引用一旦引用一个实体再不能引用其他实体5.3 常引用定义常引用是指通过const修饰的引用用于创建只读别名。void TestConstRef() { const int a 10; int b a;/*该语句编译时会出错a为常量,b的类型是int--可读可写a是只读的所以编译不通过*/ } void TestConstRef() { int c1; int dc; const int ec;//赋值可行因为C是可读可写的e变成别名是只读的 //加上const之后编译器将代码修改int temp10,const int etemp; }小总结引用取别名的时候变量访问的权限可以缩小不能放大void TestConstRef() { const int a 10; //int ra a; // 该语句编译时会出错a为常量 const int ra a; // int b 10; // 该语句编译时会出错10为常量引用必须引用一块合法的内存空间栈、堆 const int b 10; double d 12.34; //int rd d; /* 该语句编译时会出错类型不匹配无法直接绑定 const int rd d; /* rd 和 d 是两个完全独立的实体 const int rd d; 这行代码的本质执行流程是 1. 编译器发现类型不匹配int 不能直接绑定 double于是先对 d 做**隐式类型转换**把 double 类型的 12.34 截断小数得到整数值 12。 2. 用这个转换结果在内存中创建一个**临时的 int 类型对象**匿名、不可修改。 3. 将引用 rd 绑定到这个**临时 int 对象**上同时延长临时对象的生命周期让它和 rd 共存亡。 也就是说 - rd 绑定的是「转换生成的临时 int 变量」有自己独立的内存地址 - 它和原变量 d 除了初始值来源于 d 之外**没有任何内存上的关联**是两个完全分开的对象。 因此后续修改 d 的值只会改变 d 自己内存里的数值不会同步影响那个临时 int 对象rd 的值自然也不会变。 }#include iostream using namespace std; int main() { double d 12.34; const int rd d; cout 修改前d d , rd rd endl; // 修改原变量d的值 d 99.99; cout 修改后d d , rd rd endl; return 0; } //修改前d 12.34, rd 12 //修改后d 99.99, rd 12 /* 补充对比同类型 const 引用的区别 double d 12.34; const double rd d; // 同类型的const引用 这种情况下rd 才是 d 的**直接别名**绑定的就是 d 本身没有中间临时对象。此时修改 d 的值rd 会跟着变化因为它们指向同一块内存。 总结判断口诀 - 引用和被绑定对象**类型完全一致** → 直接绑定原对象修改原变量会影响引用 - 类型不一致需要隐式转换 → 绑定转换生成的**临时对象**原变量和引用互不影响。 */上述图片案例中赋值产生了一个临时变量i构造了一个double的临时变量再把临时变量赋值给db为什么加上const就可以实现赋值不加const就报错呢变量赋值实现机制实际会创建临时变量进行转换临时变量具有常性db引用的不是i而是引用的临时变量5.4 引用的使用场景1引用作为参数参数传引用r1r2和ab共享同一内存空间而参数传值不会完成交换操作因为会开辟与ab不同的空间接收ab的值。2引用作为返回值//返回局部变量引用 int test01() { int a 10; //局部变量存储在栈区 return a; } //返回静态变量引用 int test02() { static int a 20;//静态变量存储在全局区全局区上的数据程序结束后系统释放 return a; } int main() { //不能返回局部变量的引用 int ref test01(); cout ref ref endl;//打印10因为编译器做了保留 cout ref ref endl;//打印随机值因为a的内存已经释放 //如果函数做左值等号左边那么必须返回引用 int ref2 test02(); cout ref2 ref2 endl;//打印10 cout ref2 ref2 endl;//打印10 test02() 1000;//如果函数的返回值是引用这个函数调用可作为左值 cout ref2 ref2 endl;//打印1000 cout ref2 ref2 endl;//打印1000 return 0; }核心区别:传引用返回不创建新对象直接返回原对象别名传值返回会创建临时对象拷贝int r1 Count1();编译不通过。第二种形式返回的是n的别名等价于返回的就是n第一种形式返回的是n的拷贝拷贝是临时变量临时变量具有常性再举个栗子下面这种情况下编译输出结果变为了7为什么呢答因为ret是返回值的别名执行两次函数之后打印再次调用ret的时候值会变化上述问题解决办法c变量使用static进行修饰static int cab第一次执行才会初始化第二次咱再进去的时候a3,b4并不会在执行static int cab定义只会被执行一次-----------此时出了作用域c还在5.5 指针和引用的区别引用和指针的不同点:引用概念上定义一个变量的别名指针存储一个变量地址。引用在定义时必须初始化指针没有要求引用的本质是一个指针常量引用在初始化时引用一个实体后就不能再引用其他实体而指针可以在任何时候指向任何一个同类型实体没有NULL引用但有NULL指针在sizeof中含义不同引用结果为引用类型的大小但指针始终是地址空间所占字节个数(32位平台下占4个字节)引用自加即引用的实体增加1指针自加即指针向后偏移一个类型的大小有多级指针但是没有多级引用访问实体方式不同指针需要显式解引用引用编译器自己处理引用比指针使用起来相对更安全六、类联函数定义在函数声明前添加inline关键字使函数在调用处直接展开而非调用作用机制编译器将函数体代码直接插入到每个调用点避免函数调用的开销语法示例inline void swap(int a, int b) { int temp a; a b; b temp; }类联函数的底层原理空间换时间通过增加代码体积空间来减少函数调用开销时间调用开销对比普通函数调用1万次需要1万次建立函数栈帧1000010类联函数展开后代码量增加10行函数调用1万次→10万行指令七、auto关键字基本概念auto是C11引入的关键字用于自动推导变量的类型推导规则根据初始化表达式自动确定变量类型示例int a 10; auto b a; 此时b的类型自动推导为int引用类型推导auto c a; c的类型仍然是int因为它是a的别名类型查看使用typeid(变量).name()可以查看变量类型示例typeid(a).name()返回int类型信息使用限制不能用于函数参数声明不能用于数组类型推导同一行多个auto变量必须推导出相同类型实际应用简化复杂类型声明特别是模板类型示例遍历map容器时替代冗长的迭代器类型声明使代码更简洁易读避免重复书写复杂类型注意事项推导引用类型时需显式使用推导指针类型时*可省略不能用于不完整的类型推导底层原理编译时进行类型推导需要完整的初始化表达式才能确定类型本质上是一种编译期类型推断机制范围for用法示例传统遍历方法需要手动计算数组大小并使用索引如for(int i0; isizeof(array)/sizeof(int); i) { array[i]*2; }范围for语法for(auto e : array) { e*2; }自动遍历数组每个元素使用限制仅适用于数组类型指针或退化后的数组不可用例如数组当作函数形参传递的时候数组会退化成指针不再是数组了被遍历对象必须具有明确的迭代范围八、空指针的定义C语言风格使用int* p NULL或int* p 0定义空指针C11推荐使用int* p nullptr定义空指针问题根源NULL宏被定义为字面量0本质是整型导致函数重载时可能错误匹配到整型参数版本类型安全nullptr是真正的指针类型void*避免与整型0产生歧义
返回列表