企业的网站建设费账务处理,网站建设犭金手指a排名12,wordpress自适应主题制作,建设网站设计公司从今天起#xff0c;我要开始hello#xff0c;world。
往后更要做到#xff0c;拳打数据结构#xff0c;脚踢Linux。
这就是江湖人的风范。 拼搏百天#xff0c;我要学希普拉斯普拉斯。
C是在C的基础之上#xff0c;容纳进去了面向对象编程思想#xff0c;并增加了许…从今天起我要开始helloworld。
往后更要做到拳打数据结构脚踢Linux。
这就是江湖人的风范。 拼搏百天我要学希普拉斯普拉斯。
C是在C的基础之上容纳进去了面向对象编程思想并增加了许多有用的库以及编程范式 等。
都说C是C的补充与延伸也就是针对C不足的地方进行改进那么C究竟有哪些不足呢
请看码
#include stdio.h
#include stdlib.h
int rand 10;
int main()
{printf(%d\n, rand);return 0;
}
这样的程序乍一看没有问题但是会报错报什么样的错呢 我们创建了一个全局变量rand但是在stdlib.h的库里包含着rand这个函数出现了命名冲突程序员的命名和库出现冲突库可以是标准库、第三方库等等
C语言没办法解决类似这样的命名冲突问题所以C提出了namespace来解决。
在C/C中变量、函数和类都是大量存在的这些变量、函数和类的名称将都存在于全局作用域中可能会导致很多冲突。
在提命名空间前先说一个字:域
域
请看码
#include stdio.h
int x 0;
int main()
{int x 1;printf(%d\n,x);return 0;
} 输出结果是什么呢
当然是1。
第一个x是全局变量第二个x是局部变量。
那么有没有办法访问那个身为全局变量的x呢
当然可以只需要加一个奇妙的符号域作用限定符 ::
在C中有全局域、局部域命名空间域和类域。
局部域相当于自己家的菜地全局域相当于野生菜地命名空间域相当于邻居的菜地。
不同域可定义同名变量。
编译器在搜索时秉持着这样的原则 不指定域 1.局部域 2.全局域 指定域 去指定域搜索 若都没有则报错 想要访问那个全局变量可以指定域
#include stdio.h
namespace wes1
{int x 0;
}
int main()
{int x 1;printf(%d\n,wes1::x);return 0;
}
也可以这样默认去全局搜索
#include stdio.h
int x 0;
int main()
{int x 1;printf(%d\n,::x);return 0;
} namespace
使用命名空间可以将标识符的名称进行本地化 以避免命名冲突或名字污染namespace关键字的出现就是针对这种问题的。
命名空间的概念
命名空间中可以定义变量、函数和类型
namespace wes1
{int rand 10;int Add(int left, int right){return left right;}struct Node{struct Node* next;int val;};
}
命名空间可以嵌套
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;}}
}
同一个工程中可以出现同名的命名空间编译器最后会合成于同一个命名空间中
命名空间的使用
命名空间的使用有三种方式
1.加命名空间名称及作用域限定符
int main()
{printf(%d\n, N::a); //N为命名空间的名称return 0;
}2.使用using将命名空间中某个成员引入
using N::b;
int main()
{printf(%d\n, N::a);printf(%d\n, b);return 0;
}
3.使用using namespace 命名空间名称引入
using namespace N;
int main()
{printf(%d\n, N::a);printf(%d\n, b);Add(10, 20);return 0;
}
不展开也可以访问命名空间中的内容自己携带零食展开命名空间相当于随便访问命名空间中的内容学校允许带零食 tips在遇到结构体时域作用限定符应这样
wes1 :: struct Node phead;
而不是这样
struct wes1 :: Node phead;
hello world
认识第一个C程序吧
#includeiostream
using namespace std;
int main()
{cout hello world! endl;return 0;
} std是C标准库的命名空间名C将标准库的定义实现都放到这个命名空间中这行代码
using namespace std;
意义是展开std这个命名空间若没有这句程序将会报错。但是展开容易有冲突
1. 日常练习中直接using namespace std即可方便快捷。
2. 项目开发中使用using namespace std展开标准库全部暴露出如果我们定义跟库重名的类型/对象/函数就存在冲突问题。代码较多、规模大
C输入输出
cout和cin都在iostream这个头文件下被std包裹cout和cin分别是ostream和istream类型的对象
cout标准输出对象(控制台)
cin标准输入对象(键盘)
和在C中是左移右移的意思但在C中符号进行了复用 1.左移 2.流插入运算符很形象流向对象 1.右移 2.流提取运算符 可以流很多可以自动识别类型
码
#includeiostream
using namespace std;
int main()
{int i 10;cout hello world! i\n endl;return 0;
} tipsendlendline相当于换行符
C用这种方式怎么控制输出精度呢
C兼容C用之前的方法就好。。。
cin流提取从键盘提取到变量中也可以连续提取
#includeiostream
using namespace std;
int main()
{int i 0;int p 0;cin i p;cout i p endl;return 0;
} 缺省参数默认参数
使用
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。
在调用该函数时如果没有指定实 参则采用该形参的缺省值否则使用指定的实参。
#includeiostream
using namespace std;
void Func(int a 0)
{cout a endl;
}
int main()
{Func(); // 没有传参时使用参数的默认值Func(10); // 传参时使用指定的实参return 0;
}
分类
全缺省参数
void Func(int a 10, int b 20, int c 30)半缺省参数
void Func(int a, int b 10, int c 20)tips 1. 半缺省参数必须从右往左依次给出不能间隔着给 2. 缺省参数不能在函数声明和定义中同时出现如果声明与定义位置同时出现恰巧两个位置提供的值不同那编译器就无法确定到底该用哪个缺省值在声明处给编译阶段才不会报错 3.缺省值必须是常量或者全局变量 4. C语言不支持编译器不支持 函数重载
函数重载就是一词多义是函数的一种特殊情况。
C允许在同一作用域中声明几个功能类似的同名函数这些同名函数的形参列表(参数个数或类型或类型顺序)不同常用来处理实现功能类似数据类型不同的问题
一个变量可以有多个引用多个别名
参数类型不同
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(10, 20);Add(10.1, 20.2);
}参数个数不同
void f()
{cout f() endl;
}
void f(int a)
{cout f(int a) endl;
}
int main()
{f();f(10);
}参数类型顺序不同
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()
{f(10, a);f(a, 10);return 0;
}
C支持函数重载的原理--名字修饰
为什么C支持函数重载而C语言不支持呢
因为C语言在链接时直接用函数名去找地址不允许同名函数。
C引入函数名修饰规则由 各个编译器自己实现。
以Linux为例
在linux下采用gcc编译完成后函数名字的修饰不变。
在linux下采用g编译完成后函数名字的修饰发生改变编译器将函数参数类型信息添加到修改后的名字中g的函数修饰后变成 _Z函数长度 函数名类型首字母。
C通过函数修饰规则来区分只要参数不同修饰出来的名字就不一样就支持了重载。如果两个函数函数名和参数是一样的返回值不同是不构成重载的因为调用时编译器没办法区分。
引用
概念
引用不是新定义一个变量而是给已存在变量取了一个别名编译器不会为引用变量开辟内存空 间它和它引用的变量共用同一块内存空间。
怎么取别名呢祖师爷又复用了以前的一个符号
void TestRef()
{int a 10;int ra a; //定义引用类型printf(%p\n, a);printf(%p\n, ra);
}
ra是a的别名也可以给别名取别名。抓我鲁迅当然和你周树人有关系
tips引用类型必须和引用实体是同种类型的
特点 1. 引用在定义时必须初始化 2. 一个变量可以有多个引用 3. 引用一旦引用一个实体再不能引用其他实体 void TestRef()
{int a 10;// int ra; // 编译时出错int ra a;int rra a;printf(%p %p %p\n, a, ra, rra);
}
常引用
三种情况都在这啦
void TestConstRef()
{const int a 10;//int ra a; // 编译出错a为常量const int ra a;// int b 10; // 编译出错b为常量const int b 10;double d 12.34;//int rd d; // 编译出错类型不同const int rd d;
}使用场景
1.做参数
在C语言阶段我们应该都写过这样一个函数交换两数的值
#includestdio.h
void Swap(int* a, int* b)
{int tmp *a;*a *b;*b tmp;
}
int main()
{int x 0;int y 1;printf(%d %d\n, x, y);Swap(x, y);printf(%d %d\n, x, y);return 0;
}
由于形参是实参的一份临时拷贝对形参的修改不影响实参所以我们想要实现交换两数就必须进行传址调用。
但是在C引入了引用这个概念后就会变得方便很多
#includestdio.h
void Swap(int a, int b)
{int tmp a;a b;b tmp;
}
int main()
{int x 0;int y 1;printf(%d %d\n, x, y);Swap(x, y);printf(%d %d\n, x, y);return 0;
}
在这里a被看做是x的别名b被看做是y的别名。
指针和引用功能类似近乎重叠那么引用是否可以完全替代指针呢
都说C是在C的基础上做出的完善而C的引用是对指针使用比较复杂的场景进行的替换让代码更简单易懂但是不能完全替代指针。
原因引用定义后不可改变指向
这也就不能用引用代替指针完成链表、二叉树等的增加删除等操作。
那Java、Python有没有指针呢
并没有。
那它们是怎么实现链表的呢
用引用。
好一个以子之矛攻子之盾bushi
其实Java、Python等语言变量定义出来就是引用并且可以改变指向和C嘎嘎不一样哦引用就是不能替代指针指针就是坠吊的
总结一下引用在做参数方面的杰出贡献
1.做输出型参数
2.对象比较大减少拷贝提高效率
2.做返回值
请看这段代码
#includestdio.h
int func()
{int a 0;return a;
}
int main()
{int ret func();return 0;
}
在这里函数的返回值是由寄存器带回的而不是返回a出作用域就销毁的临时变量 指针在使用的时候可能会出现野指针那么引用有没有可能出现野引用呢
这就是
#includeiostream
using namespace std;
int func()
{int a 0;return a;
}
int main()
{int ret func();cout ret endl;return 0;
}
将a的别名返回了访问已经还给操作系统的空间造成了野引用。
结果认为是随机值
由于栈帧空间是可以重复使用的所以
#includeiostream
using namespace std;
int func()
{int a 0;return a;
}
int fx()
{int b 1;return b;
}
int main()
{int ret func();cout ret endl;fx();cout ret endl;return 0;
}
这段代码最终结果是 感兴趣的话可以康康函数栈帧的创建与销毁-CSDN博客
变量出了函数作用域生命周期结束要销毁局部变量不能用引用返回薛定谔的猫
若是全局变量、静态变量、堆上的空间等则可以用引用返回。
tips
以值作为参数或者返回值类型在传参和返回期间函数不会直接传递实参或者将变量本身直接返回而是传递实参或者返回变量的一份临时的拷贝因此用值作为参数或者返回值类型效率是非常低下的尤其是当参数或者返回值类型非常大时效率就更低。
传值和指针在作为传参以及返回值类型上效率相差很大。
引用和指针的区别 1. 引用概念上定义一个变量的别名指针存储一个变量地址 2. 引用在定义时必须初始化指针没有要求 3. 引用在初始化时引用一个实体后就不能再引用其他实体而指针可以在任何时候指向任何一个同类型实体 4. 没有NULL引用但有NULL指针 5. 在sizeof中含义不同引用结果为引用类型的大小但指针始终是地址空间所占字节个数(32 位平台下占4个字节) 6. 引用自加即引用的实体增加1指针自加即指针向后偏移一个类型的大小 7. 有多级指针但是没有多级引用 8. 访问实体方式不同指针需要显式解引用引用编译器自己处理 9. 引用比指针使用起来更安全 指针肯定要开辟空间那引用需不需要开辟空间呢
引用从语法上不需要开辟空间但引用的底层实现开辟了空间引用底层是用指针实现的语法含义和底层实现背离
这是汇编代码 明确一点在汇编层面上没有引用都是指针引用编译后也转换成指针了
内联函数
在C语言中使用函数会有一定开销创建函数栈帧如果需要频繁调用一个函数那可能造成的开销就过大最终导致栈溢出或者怎么怎么那C是如何解决这个问题的呢
使用宏
复习一下宏宏在预处理阶段进行替换不是函数没有分号括号控制优先级
例
#define ADD(a,b) ((a)(b))
在这个宏定义中为何要加里面的括号 因为a或者b可能是表达式不加可能会由于运算符优先级出错 宏优点 1.增强代码的复用性 2.提高性能。 宏缺点 1.语法复杂、坑多、不容易控制 2.不能调试 3.没有类型安全的检查 基于这些呢祖师爷就想着做出一些创作规避这些缺点内联函数应运而生
以inline修饰的函数叫做内联函数编译时C编译器会在调用内联函数的地方展开没有函数调 用建立栈帧的开销内联函数提升程序运行的效率。
inline是一种以空间换时间的做法如果编译器将函数当成内联函数处理在编译阶段会用函数体替换函数调用在debug版本中内联函数默认是不展开的在release模式下会展开。若想要在debug模式下展开内联函数则需要对编译器进行设置 内联也不是完美的也有缺陷当函数过大就不适合定义成内联了会导致代码膨胀程序变大。
程序变大就变大呗有啥坏处呢
举个例子
墨墨喜欢打王者她苹果和安卓都有号双开的她在更新游戏时发现了小小的不同诶嘿苹果的这个居然更新的那么快明明都是一样的内容呀墨墨开始巴拉巴拉她家网线但是后面发觉不对劲网肯定一样啊一看占用内存嚯ios下更新的内容五百多兆安卓下更新的快到1G了根网速没关系只是ios下的那个版本小一些所以变大肯定不好啦
缺陷可能会使目标文件变大
优势少了调用开销提高程序运行效率
inline对于编译器而言只是一个建议不同编译器关于inline实现机制可能不同一般建议将函数规模较小(即函数不是很长具体没有准确的说法取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰否则编译器会忽略inline特性
内联不建议声明和定义分离分离会导致链接错误inline被展开就没有函数地址了链接就会找不到嵌套以后舍去一个预处理阶段不展开会出问题
可以康康这个C/C程序内存划分及程序执行-CSDN博客https://blog.csdn.net/chestnut_orenge/article/details/135521062 那再顺便墨迹一嘴为什么要声明和定义分离
链接错误可能就出现找不到定义这是由于函数在使用前没看到声明但其实还有一种错误找到一个或多个多重定义的符号这是咋回事捏
就比如你在某个头文件下定义了一个函数然后有两个源文件都包含了这个头文件在展开的时候就会生成两个.o文件从而发生重定义不构成函数重载编译器认为你定义的是同名函数
至于这个是干嘛的呢
#pragma once
解决头文件包含重复防止头文件重复展开的
那假如我就是想要声明和定义在一起有没有解决办法捏
当然有
用static
还记得static修饰函数怎么样么
会改变函数的链接属性只在当前文件可用
还有别的方式
就是内联内联不支持声明和定义分离
地址加载到符号表是为了方便调用而内联函数的底层逻辑是展开不涉及调用
所以总结一下使用方法就是 小函数用内联 大函数用静态 auto关键字
使用
平时我们定义变量这样定义
int i 0;
int j i;
不过也可以这样定义
int i 0;
int j i;
auto m j;
auto的作用是自动类型识别 和引用类似必须初始化根据右面推导左面就像是类型声明时的“占位符”
那它设计出来有什么价值呢
请看这个
void(*pf)(int, int) func;
这是一种函数指针
玩波花的可以这样
auto pf func; 让它自己去识别嘛
在用用auto声明指针类型时用auto和auto*没有任何区别但是用auto声明引用类型时则必须加
int x 10;
auto a x;
auto* b x;
auto c x;
在同一行定义多个变量时这些变量必须是相同的类型否则编译器将会报错编译器实际只对第一个类型进行推导然后用推导出来的类型定义其他变量
tips在C嘎嘎中可以这样打印类型
int(*pf1)(int, int) func;
auto pf2 func;
cout typeid(pf1).name() endl;
cout typeid(pf2).name() endl;
结果 auto不能做参数支持做返回值不能直接用来声明数组
基于范围的for循环
在平常我们一般怎么遍历数组
这样对吧
int array[] { 1, 2, 3, 4, 5 };
for (int i 0; i sizeof(array) / sizeof(array[0]); i)array[i] * 2;
for (int* p array; p array sizeof(array)/ sizeof(array[0]); p)cout *p endl;但是对于一个有范围的集合而言由程序员来说明循环的范围是多余的有时候还会容易犯错因此C11中引入了基于范围的for循环。
在基于范围的for循环中循环后的括号由冒号“ ”分为两部分第一部分是范围内用于迭代的变量第二部分表示被迭代的范围
int array[] { 1, 2, 3, 4, 5 };
for(auto e : array)e * 2;
for(auto e : array)cout e ;
依次取数组中的值赋值给e自动迭代自动判断结束这时的e相当于数组中值的拷贝就跟函数形参实参一样不改变实际值加个引用就好了
加引用后
int array[] { 1, 2, 3, 4, 5 };
for(auto e : array)e * 2;
for(auto e : array)cout e ; tips和普通循环类似可以用continue来结束本次循环也可以用break来跳出整个循环
指针空值
在C中我们怎么表示空指针这样对吧
int* p1 NULL;
那又有什么问题在导致需要在C中做出改良呢
请看
void f(int)
{coutf(int)endl;
}
void f(int*)
{coutf(int*)endl;
}
int main()
{f(0);f(NULL);f((int*)NULL);return 0;
}这个程序的本意是想通过NULL调用指针版本的f(int*)函数但是别忘了
NULL是个...宏宏啊宏常量
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
值是0的宏常量和我们这次调用的初衷相悖了
在C98中字面常量0既可以是一个整形数字也可以是无类型的指针(void*)常量但是编译器 默认情况下将其看成是一个整形常量如果要将其按照指针方式来使用必须对其进行强转(void *)0
然后呢就觉得这样使用不太方便所以在C11中引入了一个关键字nullptr
所以以后初始化指针就可以用nullptr了
int* p nullptr; 为了挺高代码健壮性以后就都用nullptr啦