网站建设公司生存,网站建设与管理说课稿,忻州网站建设,网站的困难上次介绍了#xff1a;开启C之旅#xff08;上#xff09;#xff1a;探索命名空间与函数特性#xff08;缺省参数和函数重载#xff09;
今天就接着进行c入门的知识讲解 文章目录 1.引用1.1引用概念1.2引用特性1.3常引用其他情况 1.4引用使用场景1.4.1做参数1.4.2做返回…上次介绍了开启C之旅上探索命名空间与函数特性缺省参数和函数重载
今天就接着进行c入门的知识讲解 文章目录 1.引用1.1引用概念1.2引用特性1.3常引用其他情况 1.4引用使用场景1.4.1做参数1.4.2做返回值 1.5引用与指针的区别 2.内联函数2.1内联函数概念2.2内联函数特性 3. auto关键字3.1概念3.2auto的使用细则3.3auto不能使用的场景 4.基于范围的for循环(C11)4.1范围for的语法4.2范围for的使用条件 5.指针空值nullptr(C11) 1.引用
1.1引用概念 引用不是新定义一个变量而是给已存在变量取了一个别名编译器不会为引用变量开辟内存空间它和它引用的变量共用同一块内存空间。通过引用你可以使用一个变量的多个名称来访问和修改它的值 定义形式 类型 引用变量名 引用实体这里就不是c中大家熟知的取地址了 注意引用类型必须和引用实体是同种类型的 int main()
{int a 10;int b a;cout a endl;b 1;cout a endl;return 0;
}二者也是共用一块内存空间 1.2引用特性 引用在定义时必须初始化一个变量可以有多个引用(可以起多个别名)引用一旦引用一个实体再不能引用其他实体不能改变指向 int main()
{int a 0;int b a;int c 10;b c;//那这个到底是 c变成d的别名还是d赋值给creturn 0;
}因为引用不能改变指向这个是赋值 1.3常引用
int main()
{const int a 10;int ra a;//这样编译器会报错return 0;
}实际上这样会扩大权限本身用const修饰后不能改变a的值了但是如果引用后就能利用引用改变。这样扩大了权限 这时使用常引用 int main()
{const int a 10;const int ra a;//这样才对,没有扩大权限return 0;
}其他情况
对非常量定义常引用
int main()
{int a 10;const int ra a;//这样也可以不会报错return 0;
}权限缩小是没问题的 隐式类型转换截断强制类型转换 在 C 中隐式类型转换、截断和强制类型转换都可能导致临时变量的创建并且这些临时变量通常具有常量性质那就需要用常引用 加了const就好了 在 C 中进行类型转换时通常会创建一个临时变量来存储转换后的结果。这个临时变量是一个匿名对象它存储了转换后的值但并不会影响原始变量的值。这也是为什么对a进行类型转换后赋值但是a不发生变化 1.4引用使用场景
1.4.1做参数 在函数中使用引用作为参数可以让你直接操作传递给函数的变量而不是对其进行复制。这样可以避免复制大型对象提高效率同时允许函数修改传递的变量值(可以简单理解为我们把别名传了过来当然能通过别名来改变本身) void Swap(int a, int b)//交换两个整形
{int temp b;b a;a temp;
}1.4.2做返回值 在 C 中函数可以返回引用以避免在返回函数结果时产生拷贝。直接返回传递的变量的引用允许你对该变量进行操作。然而使用引用作为返回值需要小心确保引用所指向的变量在函数返回后仍然有效 使用引用作为返回值的语法是在函数声明或定义中将函数返回类型声明为引用类型。 但是注意
不能返回局部变量的引用
int Add(int a, int b)
{int c a b;return c;//返回了局部变量的引用
}int main()
{//有问题的代码这里不能用引用返回否则为一个不确定的值int ret Add(1, 2);cout Add(1, 2) is : ret endl;Add(3, 4);cout Add(1, 2) is : ret endl;return 0;
}在这里返回值是不是随机值取决于是否清理栈帧没有清理就是原值清理了就是随机的了由于我是用VS编译器VS出栈没有清理栈帧所以导致这里打印出的是需要的到的值ret始终是函数调用时使用的空间里变量c的别名 出了函数作用域返回对象(局部变量)就销毁了不能用引用返回否则结果是不确定 最好返回指向全局变量、静态变量、或动态分配内存的引用确保引用在函数返回后仍然有效 如果使用static来解决上述问题一定把静态变量初始化和赋值分开
int Add(int a, int b)
{static int c a b;return c;
}int main()
{int ret Add(1, 2);cout Add(1, 2) is : ret endl;Add(3, 4);cout Add(3, 4) is : ret endl;return 0;
}仍然有问题因为 static 关键字用在局部变量上表示该变量在程序运行期间只初始化一次,后续再次调用时不走那条语句了直接return c int Add(int a, int b)
{static int c ;c a b;//分开就行了return c;
}1.5引用与指针的区别
相信大家一开始都会抱有疑问引用现在能做的指针不也都可以吗 还有那个别名的底层是什么怎么理解 虽然在底层里创建的引用变量实际是有空间的可以通过汇编来观察引用是按照指针方式来实现的 但是
在语法上我们只是给那个空间取了一个别名没有开辟空间
int main()
{char a 1;char b a;//如果开了空间大小是4没有就是1cout sizeof(b);return 0;
}其他区别 引用概念上定义一个变量的别名指针存储一个变量地址。引用在定义时必须初始化指针没有要求引用在初始化时引用一个实体后就不能再引用其他实体而指针可以在任何时候指向任何一个同类型实体没有NULL引用但有NULL指针在sizeof中含义不同引用结果为引用类型的大小但指针始终是地址空间所占字节个数(32位平台下占4个字节)引用自加即引用的实体增加1指针自加即指针向后偏移一个类型的大小有多级指针但是没有多级引用访问实体方式不同指针需要显式解引用引用编译器自己处理引用比指针使用起来相对更安全 2.内联函数
在学习c时我们认识了宏 优点 1.增强代码的复用性 2.提高性能。 缺点 1.不方便调试宏因为预编译阶段进行了替换 2.导致代码可读性差可维护性差容易误用。 3.没有类型安全的检查 为了解决缺点c中采用 常量定义 换用const enum 短小函数定义 换用内联函数 2.1内联函数概念 以inline修饰的函数叫做内联函数编译时C编译器会在调用内联函数的地方展开没有函数调用建立栈帧的开销内联函数提升程序运行的效率(用展开函数体来替代函数调用) 我们使用内联函数 2.2内联函数特性 inline是一种以空间换时间的做法如果编译器将函数当成内联函数处理在编译阶段会用函数体替换函数调用。 缺陷可能会使目标文件变大。 优势少了调用开销提高程序运行效率。inline对于编译器而言只是一个建议会不会真的使用看编译器自己决定不同编译器关于inline实现机制可能不同一般情况将函数规模较小(即函数不是很长具体没有准确的说法取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰否则编译器会忽略inline特性inline不建议声明和定义分离分离会导致链接错误。因为inline被展开就没有函数地址了链接就会找不到 关于第三点内联函数因为直接展开也就不要地址查询内联函数名不会进入符号表我们之前经常在头文件里进行声明一个源文件里面进行实现。现在在其他源文件里使用内联函数时不行的 3. auto关键字
随着程序越来越复杂程序中用到的类型也越来越复杂经常体现在
类型过长难于拼写含义不明确导致容易出错
auto就是来解决这个问题
3.1概念 C11中标准委员会赋予了auto全新的含义即auto不再是一个存储类型指示符而是作为一个新的类型指示符来指示编译器auto声明的变量必须由编译器在编译时期推导而得 int main()
{auto a 1;auto b 1.1;auto c c;//但这些都不是主要的使用场景cout typeid(a).name() endl;cout typeid(b).name() endl;cout typeid(c).name() endl;return 0;
}3.2auto的使用细则
auto与指针和引用结合起来使用 用auto声明指针类型时用auto和auto*没有任何区别但用auto声明引用类型时则必须加 在同一行定义多个变量 当在同一行声明多个变量时这些变量必须是相同的类型否则编译器将会报错因为编译器实际只对第一个类型进行推导然后用推导出来的类型定义其他变量 3.3auto不能使用的场景 auto不能作为函数的参数auto不能直接用来声明数组为了避免与C98中的auto发生混淆C11只保留了auto作为类型指示符的用法auto在实际中最常见的优势用法就是跟以后会讲到的C11提供的新式for循环还有lambda表达式等进行配合使用 4.基于范围的for循环(C11)
4.1范围for的语法
之前我们写c的时候在C98中如果要遍历一个数组可以按照以下方式进行
void Test1()
{int arr[] { 1, 2, 3, 4, 5 };for (int i 0; i sizeof(arr) / sizeof(arr[0]); i){arr[i] * 2;}for (int* p arr; p arr sizeof(arr) / sizeof(arr[0]); p){cout *p endl;}
}现在我们可以这样
void Test2()
{int array[] { 1, 2, 3, 4, 5 };for (auto e : array)//用for(int e:array)也可以{e * 2;}for (auto e : array){cout e ;}
}C11中引入了基于范围的for循环。for循环后的括号由冒号“ ”分为两部分第一部分是范围内用于迭代的变量第二部分则表示被迭代的范围 基于范围的for循环会依次将容器中的元素赋值给迭代变量通常命名为element。在每次循环迭代中迭代变量将会被赋值为容器中的下一个元素直到遍历完整个容器 如果想要改变数组里就使用引用
void Test2()
{int array[] { 1, 2, 3, 4, 5 };for (auto e : array){e * 2;cout e ;}cout endl;for (auto e : array){cout e ;}
}4.2范围for的使用条件
for循环迭代的范围必须是确定的 对于数组而言就是数组中第一个元素和最后一个元素的范围对于类而言应该提供begin和end的方法begin和end就是for循环迭代的范围 迭代的对象要实现和的操作 5.指针空值nullptr(C11)
我们经常使用的NULL实际上是一个宏在传统的C头文件(stddef.h)中可以看到如下代码
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif可以看到NULL可能被定义为字面常量0或者被定义为无类型指针(void)的常量。不论采取何种定义在使用空值的指针时都不可避免的会遇到一些麻烦* 所以我们使用nullptr来对指针进行初始化来替代NULL以免NULL定义为0时出现错误 注意
在使用nullptr表示指针空值时不需要包含头文件因为nullptr是C11作为新关键字引入的。在C11中sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。为了提高代码的健壮性在后续表示指针空值时建议最好使用nullptr 好啦c入门的知识先到这里啦下面就要开启面向对象的篇章了。感谢大家支持