货架网站开发,西安php网站制作,广州手机app软件开发,服务平台收件箱目录
1 引用
1.1引用的概念
1.2 引用的特性
2 传值#xff0c;传引用的效率
3 引用和指针的区别
4 内联函数
4.1 内联函数的定义
4. 2 内联函数的特性
5 关键字auto
5.1关于命名的思考
5.2 关于auto的发展
5.3 auto使用规则
6 范围for的使用
7 空指针 1 引用
…目录
1 引用
1.1引用的概念
1.2 引用的特性
2 传值传引用的效率
3 引用和指针的区别
4 内联函数
4.1 内联函数的定义
4. 2 内联函数的特性
5 关键字auto
5.1关于命名的思考
5.2 关于auto的发展
5.3 auto使用规则
6 范围for的使用
7 空指针 1 引用
1.1引用的概念
人有外号程序中的变量也可以有不懂二级指针的人有福了祖师爷在加小语法的时候觉得使用二级指针太麻烦了索性加入引用的概念也就是给成员变量取别名该别名和成员变量共用一块空间就像李逵外号黑旋风一样使用方式是类型后面加个
int main()
{int a 1;int b a;cout a b;return 0;
}
这个时候b就是a我们对b进行修改的同时也会修改a。
1.2 引用的特性
外号可以有一个也可以有多个所以
int main()
{int a 1;int b a;int c b;int d c;cout a b c d;return 0;
} 对一个变量取多个“外号”也是没有问题的但是引用一旦成立该引用类型就不能再去引用其他元素就像黑旋风不能是浪里白条一样。
int main()
{int a 1,c 1;int b a;int b c;return 0;
}
此时代码就会报错因为b重定义了。
引用的时候一定要初始化不然就像是先选个外号看谁像这个外号再给谁按上去这是不行的所以引用之前一定要初始化
int a;
这种就是错误的代码没有引用实体。
我们在引用的时候还要避免一个问题——权限放大。
int main()
{const int a 1;int b a;int c a;return 0;
}
int c a这行代码是无误的赋值是没有问题的但是int b这行代码就有问题了
因为a被const修饰了所以a不能被修改但是引用类型是int就代表可以被修改所以这里存在权限放大的问题解决方法就是:
int main()
{const int a 1;const int b a;return 0;
}
引用类型和引用实体保持一致就行就不会存在权限放大的问题引用的实现我们就需要保证一个点引用类型和引用实体是一个级别。那么有人问了权限放大了不行权限缩小会怎么样
int main()
{int a 1;const int b a;return 0;
}
是没有问题的代码编译也都跑得过去所以权限缩小是可以的。
权限放大看着是很好理解的那么这段代码呢
int main()
{int a 1, b 1;int x a b;return 0;
}
a b的结果是int没错吧那么可不可以用int来引用呢实际上是不可以的因为a b是一个常值就跟被const修饰了一样所以要引用只能加一个const。
int main()
{ double d 12.34;const int i d;return 0;
}
如果存在类型转化但是用了const修饰也是可以引用的。 这是因为类型转换的时候存在一个临时变量这个临时变量是常性的所以用了const修饰才能引用。 2 传值传引用的效率
引用被发明来就是为了省事的比如单链表的实现那里二级指针是哪里都有那么有了引用原来的参数即写法就会省下一大半的时间
void SLTNode(Node** pphead, int val);
void SLTNode(Node* head, int val);
有了引用就不用考虑二级指针越界访问等问题了也没有解引用的问题了是非常方便的。
我们可以认为传引用就是传我们要修改的那个元素进去
void swap(int x, int y)
{int tem x;x y;y tem;
}
int main()
{int a 1, b 2;swap(a, b);cout a b;return 0;
}
最后是可以成功交换a 和 b 的值的我们就不用单独使用指针了
实现某些功能的时候我们传值也可以传引用也可以具体哪个的效率高呢函数在接收参数或者返回参数的时候传值都是进行临时的拷贝数据量一旦大了起来效率是十分低下的
struct A
{int arr[10000];
};
void Test1(A a)
{}
void Test2(A a)
{}
int main()
{A a;size_t begin1 clock();for (size_t i 0; i 10000; i)Test1(a);size_t end1 clock();size_t begin2 clock();for (size_t i 0; i 10000; i)Test2(a);size_t end2 clock();cout Test1-time: end1 - begin1 endl;cout Test2-time: end2 - begin2 endl;return 0;
}
我们创建一个结构体成员变量是40000个字节的一个数组那么如果我们传值调用如Test1用一个40000字节的数组去拷贝实参效率很低下如果我们传引用的话就相当于我们直接对arr数组进行修改没有单独开辟一块空间去拷贝效率自然就高了起来。使用时间函数来对比一下就知道了
因为Test2的时间太短了小于1ms所以打印出来的结果是0。
struct A
{int arr[10000];
}a;
A Test1()
{return a;
}
A Test2()
{return a;
}int main()
{size_t begin1 clock();for (size_t i 0; i 10000; i)Test1();size_t end1 clock();size_t begin2 clock();for (size_t i 0; i 10000; i)Test2();size_t end2 clock();cout Test1-time: end1 - begin1 endl;cout Test2-time: end2 - begin2 endl;return 0;
}
返回值也是一样的道理如果我返回的是一个值那么返回的就是一份临时拷贝效率是十分低下的 3 引用和指针的区别
从语法上看引用和被引用的对象共用一块空间
int main()
{int a 1;int ra a;cout a endl;cout ra endl;return 0;
}打印出来的地址也是一样的。
如果从汇编代码看底层来讲引用也是有自己的独立空间的
结合使用了指针的汇编来看的话它们的底层实现是一样的所以引用实际上也是开辟了空间的。
实际上引用和指针的区别是比较大的这里就列举几点比较重要的 1·引用在定义时必须初始化指针没有要求
2· 引用在初始化时引用一个实体后就不能再引用其他实体而指针可以在任何时候指向任何 一个同类型实体
3·没有NULL引用但有NULL指针
4·有多级指针但是没有多级引用
5·引用比指针使用起来相对更安全
6·访问实体方式不同指针需要显式解引用引用编译器自己处理 4 内联函数
4.1 内联函数的定义
内联函数是被inline修饰的函数使用频繁且代码量小的情况下会使用内联函数实际上就是一种空间换取时间的做法因为编译期间会在函数处将函数代码展开有点像预处理期间的头文件展开那么为什么是空间换时间呢 因为函数展开了就相当于把一段代码放过去没有单独的函数栈帧开销所以时间上会省事但是因为代码量的增加所以生成的可执行文件占用的存储空间是会变大的一旦内联函数的代码量大了一点频繁使用之后可执行程序的内存大小加的可不是一点所以要求内联函数的代码量是少量代码。
int Add(int x,int y)
{return x y;
}int main()
{int ret 0;ret Add(1, 2);return 0;
} 我们知道call一个函数就是给函数一个地址并给它开辟函数栈帧那么没有被inline修饰之前是有call的在被inline修饰之后
inline int Add(int x,int y)
{return x y;
}int main()
{int ret 0;ret Add(1, 2);return 0;
} 发现Add那里没有call对应的汇编代码在release模式下我们只需要看有没有call就行在debug模式下我们要进行相应的设置 要在这两个地方修改
可以看到依旧是没有关于call的汇编代码直接就相加了这就是内联函数空间换取时间的做法。
那儿有人不解编译阶段代码展开不就类似于宏定义吗直接定义一个宏难道不香吗
对于宏宏有时候确实是方便的但是实现复杂的功能的时候宏有时候非常抽象更重要的一点是宏不能调试在括号等小细节上容易出错也没有类型检查所以该使用内联函数的时候还是使用内联函数吧!
4. 2 内联函数的特性
虽然内联函数是空间换时间的但是实现的时候还得看编译器不同编译器关于inline的实现机制可能不同一般来说将函数规模较小不是递归频繁调用的函数采用inline修饰。
在CPrime第五版中关于内联函数是这样建议的 内联函数不建议分离和定义分离这样会导致链接的时候找不到函数的地址
// F.h
#include iostream
using namespace std;
inline void Func(int i);
// F.cpp
#include F.h
void Func(int i)
{cout i endl;
}
// main.cpp
#include F.h
int main()
{Func(10);return 0;
} 5 关键字auto
5.1关于命名的思考
学习auto之前我们思考一个问题就是随着程序的复杂程度命名也变成了一个难点命名长的时候体现在类型名难于拼写含义不够明确。
如下代码
#include string
#include map
int main()
{std::mapstd::string, std::string m{ { apple, 苹果 }, { orange,
橙子 }, {pear,梨} };std::mapstd::string, std::string::iterator it m.begin();while (it ! m.end()){//....}return 0;
}
std::map::iterator std::string::iterator是一个类型难于拼写的同时含义还不够明确那么typedef可以解决吗typedef 可以解决一些命名问题但是碰到这种就哦豁了
typedef char* pc;
int main()
{const pc p1;const pc* p2;return 0;
} p1是会报错的因为const修饰是指针本身代码可以理解为char* const p1,那么const修饰的指针是要初始化的第二个不报错因为可以理解成const char* p2修饰的是指向的对象可以不用进行初始化所以第一个会报错。
即有时候赋值给变量是含义不明确的所以委员会给了auto一个新含义。
5.2 关于auto的发展
C11中标准委员会赋予了auto全新的含义即auto不再是一个存储类型指示符而是作为一 个新的类型指示符来指示编译器auto声明的变量必须由编译器在编译时期推导而得。
简单理解来说就是auto可以用来自动推导变量类型
typedef char* pc;
int main()
{int a 1;auto b a;auto c 12.34;cout typeid(a).name() endl;cout typeid(b).name() endl;cout typeid(c).name() endl;return 0;
}
这里介绍一个关键字typeid用法记住就行用来判断变量的类型的打印出来的结果就是int int double。
就和引用一样auto修饰变量的时候也要初始化不初始化它哪里知道变量是什么类型的呢?
5.3 auto使用规则
auto修饰指针的时候有两种修饰方法
int main()
{int a 1,b 1;auto pa a;auto* pb b;
}auto后面加不加*都是可以的。
但是auto修饰引用类型的时候是一定要加的
int main()
{int a 1;auto pa a;
}
当auto修饰多个变量的时候如果类型不同也是不可以的 auto b 1, c 2.3;这时候会报错因为初始化类型不同。
auto有两个不能推导的情况
1 auto不能用于充当函数参数
这就有点像auto修饰的变量没有初始化所以会报错
int Add(auto x)
{return 1;
}
2 auto不能用于修饰数组
int main()
{auto arr[10] { 0 };return 0;
} 6 范围for的使用
在C语言里面我们遍历一个数组通常采用for的方式
int main()
{int arr[10] { 0 };for (int i 0; i 10; i){;}return 0;
}
在C里面对于一个有确定范围的循环是可以使用范围for的省时省力for循环后的括号由冒号“ ”分为两部分第一部分是范 围内用于迭代的变量第二部分则表示被迭代的范围。
int main()
{int arr[10] { 1,2,3,4,5,6,7,8,9,0 };for (auto a : arr){cout a ;}return 0;
}
这时候auto的妙用就出来了可以自动识别类型。
这段代码的意思就是自动遍历数组arr将数组里面的元素依次放到a里面所以a是一个用来接收数组元素的一个临时变量打印的时候打印的就是a所以数组里面的元素是没有被修改的想要修改那么引用类型就可以
int main()
{int arr[10] { 1,2,3,4,5,6,7,8,9,0 };for (auto a : arr){a * 2;}for (auto a : arr){cout a ;}return 0;
}
但是范围for的话只能用于从0开始遍历不能从某个地方开始遍历或者是倒着遍历而且要求遍历的时候范围是可以确定的也就是说数组类型int arr[m]的m一定是一个确定的值。
其余和普通循环无异continue或者是break都可以正常使用。 7 空指针
NULL实际上是一个宏。 定义中表明NULL被宏定义为0或者是0强转的泛型指针所以有时候使用空指针的时候不免会出现问题
void Func(int)
{cout int endl;
}
void Func(int*)
{cout int* endl;
}
int main()
{Func(0);Func(NULL);Func((int*)NULL);
}
程序原本传NULL是为了访问int*的函数的但是因为NULL被定义为0所以打印出来的是int所以要强转为(int*)才能访问第二个函数。
在C11中为了区分开来关键字nullptr表示空指针。
在C11中sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。 以上就是C到C引入的一些小语法感谢阅读