96SEO 2026-02-19 22:54 10
3:auto关键字(C11)3.1:auto介绍代码1代码2代码3

3.2:auto的使用细则3.2.1:auto必须初始化.3.2.2:auto与指针和引用相结合代码1代码2
3.3:auto不能推导的场景3.3.1:auto不能作形式参数(C20以前)3.3.2:auto不能声明数组
4:基于范围的for循环(C11)4.1:范围for的语法代码1代码2
嘿嘿,家人们,今天我们继续就C入门往下学习,好啦,废话不多讲,开干!
引用不是新定义一个变量,而是给已经存在的一个变量取一个别名,编译器不会为引用变开辟新的空间,它与它所引用的变量共同占用同一块空间.
例如水浒传中的李逵,在家称自己为铁牛,江湖上人称黑旋风.了解了引用的概念后,我们来看下面这段代码.
}上面这段代码中,ra就是变量a的别名,我们可以清晰地看到,ra与变量a的地址一样的,因此这两个变量所共用同一块内存空间.
PS:引用的类型必须和引用的实体是相同类型的,这句话是什么意思呢?我们来看下面这段代码.
}当对变量a使用与之不同的数据类型来引用时,此时我们可以清晰地发现,是无法通过编译的,因此引用的数据类型要与引用的实体是相同的数据类型.
}通过观察上面这段代码,我们可以清晰地发现,引用在使用时必须进行初始化即指明实体.
一个变量有多个引用,这句话应该很好理解,这就好比,一个人可以有多个外号,例如,李逵有黑旋风的外号,也有铁牛的外号,但它们最终所指向的对象还是李逵.
}通过观察上面的代码我们可以清晰地发现,对ra1与ra2分别进行了以后,由于ra1和ra2都是变量a的别名,因此此时变量a也相应地进行了.
b这一行代码运行了以后并不是说ra就是变量b的别名了,因为引用的特性规定了,引用一旦引用了一个实体以后,就不能再引用其他实体,因此这里ra
b并不是改变引用的指向,而是对其进行赋值,我们通过运行这段代码可以清晰地发现,此时变量a的值发生了变化.
}在C语言阶段,实现两数交换这个函数方法时,我们是通过传变量的地址然后形参使用指针接收,接着对其进行解引用从而实现两数交换,而到了C这一阶段,我们则可以不需要使用指针,而是使用引用.
}上述代码中则是通过使用引用来实现的两数交换,我们知道引用是变量的别名,既然已经拿到了变量的别名,那么就可以直接对其进行操作,这相较于指针来讲的话更加便捷.
引用除了做参数外,还能做返回值,但是是在特定场景下引用才能做返回值,我们首先来看下面这段代码
}在C语言阶段,我们学习过,函数调用是会建立栈帧的,每次函数调用完以后会销毁栈帧,上述代码中,返回的并不是变量a,而返回的是变量a的拷贝,也就是说,在Function1函数销毁以前,将变量a的值拷贝给一个临时对象,然后将其带出去,最后再赋值给result,当Function1函数调用完以后,此时函数栈帧销毁,那块空间还给了操作系统.
在上述代码中则是传引用返回,可是,当Function2函数调用完了以后,此时函数栈帧销毁,由于是传引用返回,那么此时就再次对销毁的那块函数栈帧空间进行访问,此时就会出现问题了,那么此时所带回来的值具体是多少,我们是不知道的,这个时候就要取决于编译器是否对那块销毁的函数栈帧空间进行了清理,因此result的结果要取决于在什么样的平台下,不同的平台有不同的结果
博主使用的vs2022是没有在函数栈帧销毁之后进行清理的,因此这里result2的值为0.但是我们来看下面这个场景.
当在打印一次result2之后,我调用一次Function3函数,此时再一次打印result2的结果,我们可以清晰地发现,此时result2的结果与变量b的值相同,我们知道,时间是不能重复利用的,但是空间是可以重复利用的,当Funtcion2函数栈帧销毁后,此时调用Function3,因此Function3的函数栈帧建立,在调用完以后并销毁,由于空间能够重复利用,因此Function3的函数栈帧复用了Function2的函数栈帧,由于博主使用的vs2022编译器没有在函数栈帧销毁之后对那块空间进行清理,因此此时reuslt的值为1
因此,如果函数调用结束时,出了函数作用域,若对象还在,则可以使用引用返回,如果已经还给了系统,则必须使用传值返回.那么到底哪些情况下可以使用传引用返回呢?我们来看下面这段代码.
std;//全局变量/静态变量/堆区上的变量等就可以用引用返回
}上述代码中,使用static关键字来修饰局部变量,在C语言阶段,我们学习过,原本普通的局部变量是放在栈区的,但**被static关键字修饰以后,放在了内存的静态区,为静态变量,静态区的变量的生命周期和全局变量的生命周期一样,程序结束时才会结束.**因此static修饰的局部变量用引用返回,除此之外,以下两种变量可以使用引用做返回值
在C语言阶段我们学习到使用const修饰的变量称为常变量,那么在引用这块同样也有常引用,我们首先来看下面这段代码.
}上述代码中,对变量a取别名为b,那么为什么可以直接这样子做呢?因为权限是可以平移或者缩小的,原本可以直接对变量a进行修改,即变量a是可读可写的,这里引用b为a的别名,同样也是可读可写的,相当于发生了权限的平移.
}上述代码则发生了权限的缩小,C语言阶段我们学习过使用const
修饰的变量称为常变量,此时该变量是只读的,不能直接被修改,而这里原本变量a是可读可写的,然后通过const来修饰引用c,使用const修饰的引用变量称为常引用,此时引用c是只读的,由原本的可读可写到引用c变成了只读的,这就是权限的缩小.那么我们不能通过引用c来间接地修改变量a.
}上述代码中则发生了权限的放大,是不可以的;使用const修饰变量a,那么此时变量a为常变量即是只读的,而当正常使用引用去接收变量a时,此时引用c是可读可写的;也就是说从原本的只读变化到了可读可写,这就是权限的放大.
}上述代码可不可以呢答案是NO.有些uu们会有些疑惑,这里的变量a和b都没有用const关键字来修饰呐,那么为什么不能使用引用c来接收呢?是这样子的,a
b这个表达式发生完以后是一个临时变量,临时变量是具有常性的,那么既然有常性,则当正常使用引用c去接收时则会发生权限的放大.如果想正常接收的话,则需要使用常引用.
}在上述代码中,引用r肯定是不能引用变量i的,因为发生了权限的放大.那么有的uu估计就会类比,变量j也同样不能够接收变量i,因为也同样发生了权限的放大,如果这么想的话,那就是错误滴.这里变量j是可以接收i的,原因在于:这里的int
i是将i的值拷贝给变量j,那么j的改变不会影响到i,也就不存在所谓的权限放大,而引用r的改变会影响到变量i,因此才会产生权限的改变.
权限能够平移,缩小,但权限不能够放大.权限放大----指针和引用赋值才存在权限的放大.表达式的结果是个临时变量,临时变量具有常性.
有的uu就会在想,C中有引用,那么这和C语言中的指针有什么区别呢?博主将带着uu从下面这两个角度来看.
通过观察反汇编我们可以清晰地发现,在汇编层面上,没有引用,都是指针,引用在编译器经过编译后也转换为指针了,因此从底层的角度上,引用是会开空间的.
引用必须初始化,指针可以不初始化.引用是变量的别名,不开空间,而指针存储的是变量的地址,因此要开空间.引用不能改变指向,指针可以改变指向.引用自增即引用的实体增加1,指针自加即指针向后偏移一个类型的大小.在sizeof中的含义不同:sizeof(引用)结果为引用的数据类型的大小.但指针始终是地址空间所占的字节数,在32位平台下为4个字节,在64位平台下为8个字节.引用相对于指针来讲更加安全,没有空引用,但是有空指针,而且容易出现野指针,但是不容易出现野引用.有多级指针,但是没有多级引用.
在C语言阶段,我们学习过,函数每次调用都会开辟栈帧,但是当函数调用次数过多时,譬如假设我要调用100万次函数,那么就要开辟100w个函数栈帧,这样子的话,消耗是很大的,C语言阶段,我们是可以通过定义宏函数来解决这个问题的,我们看下面这段代码
}上述代码呢则是通过宏函数来实现两数相加,假设我要调用100w次这个ADd函数,那么就会开辟100w个函数栈帧,这样子会降低程序的运行速度,C语言是通过定义宏函数来解决这个问题的,但是呢宏有以下几个缺点:
(1):宏的语法相对来讲比较复杂,坑比较多,譬如可能会带来运算符优先级的问题,不容易控制.
针对宏的缺点,C则提出了内联函数的概念,通过使用inline关键字来修饰函数,在编译时C编译器会在调用内联函数的地方将其展开,从而代替建立函数栈帧的开销,这样子能够提升程序运行的效率.
}我们通过反汇编可以观察到,当没有inline关键字修饰时,此时通过call这条指令来调用Add函数,此时就会建立函数栈帧.那我们来看通过inline关键字修饰的Add函数.
PS:在此之前,我们需要做一些设置,因为博主是在Debug模式下去观察inline函数的,如果不做此设置的话,此时去观察反汇编还是会看到call指令的.如果是release模式的话,就直接对比是否有call指令就好了.
将下面的C/C展开,然后先点击常规,将调试信息格式修改为程序数据库然后点击优化,将**内联函数扩展修改为只适用于_inline(/Ob1)**即可.做好这些操作后,我们就能在Debug模式下观察下面的代码的反汇编情况了
}当我们使用inline关键字去修饰函数时,此时我们通过观察反汇编可以清晰地看到没有了call指令,而是将这个函数进行了展开.
inline是一种以空间换时间的做法,如果编译器将函数当成内联函数去处理的话,在编译阶段,会把函数当成函数体去使用.这样子的话,就会减少调用函数建立函数栈帧的开销,提高程序的运行效率.但是inline也是有缺陷的
可能会导致目标文件变大.举例简单的例子,譬如有个函数里面封装了100行代码
(1):使用inline修饰时:此时调用100w次,这样子的话就要展开100w次,就会有100
100w的代码,这样子的话可能会导致一种叫代码膨胀的现象出现.
(2):使用非inline修饰时,此时调用100w次,这个时候的代码量是100
100w左右,因为此时是通过建立函数栈帧来减少代码量的,我只要知道那个栈帧的起始地址就好啦.
因此,inline对于编译器只是一个建议,不同的编译器对于inline的实现机制可能不同,一般建议:将函数规模较小,不是递归、且频繁调用的函数可以使用inline修饰使用inline修饰的函数,不建议声明与定义分离,分离的话会导致链接错误.因为使用inline修饰的函数此时会被展开,这样子的话就没有了函数地址,链接的时候就会找不到.
随着程序越来越复杂,程序中用到的类型也越来越复杂,经常体现在:
在早期的C/C中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量然后再C11中,标准委员会重新赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型提示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得.
}在C中,我们可以使用typeid().name()这个函数来查看变量的数据类型.通过观察我们可以发现,变量a是一个整型,变量b是字符类型,pf是一个函数指针,指向Test函数.我们可以发现定义一个函数指针变量是不是比较麻烦,那么有什么简便的方法呢?有的uu就会想,这很简单,使用typedef关键字重定义一下,那我们来看下面这段代码.
}通过typedef重定义类型,我们可以发现确实会比较简便些,但是跟auto相比的话,还是差一些滴,我们来看下面这段代码.
}而当使用auto关键字后,此时推导类型的工作就不再是交给我们了,而是交给了编译器来处理,使用auto声明的变量编译器会在编译时期进行推导.
使用auto定义变量时必须对其进行初始化,在编译时,编译器需要根据其初始化的表达式来推导auto的数据类型.
}之前我们学习到引用是变量的别名,那么当使用auto声明引用时是需要带上符号的,不然编译器区分,上述代码中ra1则是单独的一个新变量,ra2才是变量a的别名,因此改变ra1是影响不到变量a的.
}当使用auto声明多个变量时,这些变量必须得是相同的类型,否则会编译器会发生报错,因为编译器首先对第一个变量的数据类型进行推导,然后根据推导出来的数据类型再去定义其他的变量.
}在C17包括C17)以前,auto是不能当作函数的形参的,因为编译器无法对其进行推导,而到了C20乃至以后,就开始支持auto做形式参数了,因此这里要看是在什么样的C标准下.
在C语言阶段,我们学习过for循环语句,并且我们在写程序时也会经常用到for循环,但是呢,在一个有范围的集合内,在使用for循环时,我们要经常说明循环的判断范围,这样子的话有些小多余,而且还比较容易犯错,因此C11中引入了基于范围的for循环.for循环后的括号由冒号:分割成两个部分:**第一部分是范围内用于迭代的变量,第二部分是被迭代的范围.**我们来看下面这几段代码.
}在上述代码,我们使用了曾经的for循环与范围for循环来遍历数组array,两者相比,范围for循环更加便捷些,不需要写循环的判断部分与调整部分,这个操作交给了编译器来进行处理.
使用范围for循环的同时,我们同样可以带上引用来改变数组中的元素值.
}PS与普通循环类似,范围for同样也能使用continue和break来跳出循环.
for循环迭代的范围必须是确定的例如对于数组而言,我们就必须知道第一个元素和最后一个元素的范围.
在C语言阶段,我们学习过,定义一个变量如果没有对其进行初始化的话,这个变量的值将会是随机值,可能会发生一些不可避免的错误,因此为了防止这种情况的发生,我们通过在定义变量时要对其给上一个合适的初始值.譬如:在指针阶段,我们学习过,指针如果不对其进行初始化的话,那么就会是个野指针,因此我们通常在初始化指针的时候会对其置NULL
}但是呢,NULL实际上是一个宏,在传统的C头文件中,可以看到如下代码:
*)0)#endif#endif可以清晰看到,*NULL被定义为字面常量0,或者被定义为无类型指针(void
)的常量,我们知道C是兼容C的,C提出了函数重载的概念,那么因此针对NULL这种情况,就会发生一些不可避免的情况,我们来看如下代码.
{Function(0);Function(NULL);Function((int*)NULL);return
}在上述代码中,Function函数构成了函数重载,因为其参数类型不同,但是当实参传入NULL以后,却没有调用原本的Function(int
val)这个函数,这就和原本的程序背道而驰了,当对NULL指针进行强制类型转换时,才调用了Function(int
*)这个函数在C98中字面常量0既可以是一个整形数字也可以是无类型的指针(void*)常量但是编译器默认情况下将其看成是一个整形常量,若要将其当成指针来看待,则要进行强转,因此在C11中则提出使用nullptr来表示指针空值.
{Function(0);Function(NULL);Function((int*)NULL);Function(nullptr);return
}PS:1:在使用nullptr表示指针空值时不需要包含头文件.
2:为了提高代码的健壮性在后续表示指针空值时建议最好使用nullptr.
好啦,uu们,关于C入门(下)这部分滴详细内容博主就讲到这里啦,如果uu们觉得博主讲的不错的话,请动动你们滴小手给博主点点赞,你们滴鼓励将成为博主源源不断滴动力.
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback