面试题

本文也发表在我另一篇独立博客qingbob.com:  《答寒冬的面试题1》

有关注前端和微博的朋友一定留意到了前一阵寒冬和玉伯关于前端面试的讨论,后来老赵和左耳朵耗子也加入其中,讨论带来了非常多的启发和思考——不久之前自己也经历了几轮面试,决心对自己的基础开始加强,正巧寒冬老师题目由浅入深,非常有代表性,有些概念自己也模糊,决心花时间把这些概念全部弄清楚,梳理一遍

寒冬的微博原文如下

面试的时候问个css的position属性能刷掉一半的人这是啥情况……其实这问题我本来打算的是可以顺着一路扯到normal flow、containing block、bfc、margin collapse,base line,writing mode,bidi,这样一路问下去的,奈何第一个问题(亲我真的只问了position有哪些取值和行为啊)就悲剧了……

并且后来寒冬后来也就这一事件写了两篇文章(《谈谈面试与面试题》, 《阿里巴巴无线前端团队》)来做了一些说明,我们就根据这些材料对知识点进行一些梳理。本文将谈到:

  • position
  • normal flow
  • containing block
  • bfc
  • margin collapse
  • writing mode
  • bidi
  • 以及各种综合和边缘情况

最困难的是当你在谈一个概念的同时不得不对其他概念有所涉及——但其实所有的概念和方案都是为了解决问题而生,我打算从最简单的布局开始,以解决复杂的布局方案为线索,逐渐引入这些概念。本文的资料来源于国外国内的通俗博客和实战经验,如果直接引用w3c的概念我感觉会有一些晦涩(重要的是我看的也很头大),但仍然会做引用。本文只是抛砖引玉,如果有不正确的地方请多多指正。

Normal Flow

我倾向把normal flow翻译为标准流。当你在一个空白的网页上插入不同的标签div, p, span等,那么这些标签形成的元素将以何种标准进行排列?那就是标准流。

谈论标准流的前提是,页面上的所有元素都没有有关布局的css(float,display,position等)进行修饰,也就是说所有的元素要么是块状元素(block),要么是行内元素(inline)

块状元素将在页面上或者包含他们的包含块(containing block)中,从左上方开始,并且是从垂直自上往下进行填充,不用担心横向,因为块级元素非常霸道,一个元素必定占一整行,即使你给它指定宽度,它所在的那一行余下的宽度也是属于它的,不会有后面的元素进来,即使后面的元素很窄能刚好插进来。而上下两个块之间的距离则根据他们的外边距和外边距的折叠规则(margin collapse)而定,这个后面会谈到。比如看下面这个例子

div { border: 1px solid black; }
div div { height: 100px; }
.a { margin: 20px 0; }
.b { margin: 30px 0;  100px; }
.c { margin: -50px 0 0; }
<div>
    <div class="a"></div>
    <div class="b"></div>
    <div class="c"></div>
</div>


 

而行内元素顾名思义,则优先进行横向排列,从容器的左上开始,后面的元素一个紧挨一个的排列在这一行中,只有当位置不够了,才会被挤到下一行去,

div {  100px; border: 1px solid black;}
span.a {background: yellow;}
span.b {background: red;}
span.c {background: blue;}
<div>
<span class="a" >This is an span element</span>
<span class="b" >This is another span</span>
<span class="c" >This is   the third span</span>    
</div>
This is an span element This is another spanThis is the third span

在标准流的状态下,如果两种元素混合在一起,那么将各自遵循各自的标准,理论上是不会有干扰的。临时想到一个问题,如果块状和行内上下相邻,并且都配备有外边距,那么外边距怎么计算?再复杂一点,行内元素所在的那一行有好几个行内元素,并且每个的外边距都不相同,那么与垂直相邻的块状元素的距离怎么算?代码如下

div {  100px; border: 1px solid black; margin: 20px;}
span.a {margin: 10px 0 0;}
span.b {margin: 20px 0 0;}
span.c {margin: 30px 0 0;}
<div class="block"></div>
<span class="a">This is a span</span>
<span class="b">This is b span</span>
<span class="c">This is c span</span>

这个问题还涉及另一个概念:盒子模型,如果又要展开又大了去了。在这里只说一点,行内元素的margin和padding只对它的左右元素有影响,对上下无影响,具体看下面代码

.p {  200px; height: 50px; border: 1px solid black;}
.span {margin: 20px;}
<p>
<span>this is span</span><span class="span">this is span</span><span>this is span</span><span>this is span</span><span>this is span</span>
</p>

this is span this is spanthis is span this is span this is span

本节主要参考资料:

BFC(Block Formatting Context)

好吧,必须承认我也是第一接触BFC的概念,很惭愧,于是疯狂的查找资料,对它概念的介绍引用阿里巴巴用户体验部的一篇文章的一段话

什么是BFC(Block Formatting Context),简单讲,它是提供了一个独立布局的环境,每个BFC都遵守同一套布局规则。例如,在同一个BFC内,盒子会一个挨着一个的排,相邻盒子的间距是由margin决定且垂直方向的margin会重叠。而float和clear float也只对同一个BFC内的元素有效。

在提BFC的同时也不得不提另一个概念,IE的haslayout属性,一旦元素的这两个属性(haslayout在IE下)被触发,他们都能给自己提供一个独立的布局环境(我的疑问在于不独立的布局环境又是什么情况?)。当元素的CSS属性设置了下列之一时,即可创建一个BFC:

那么如何触发BFC,继续引用一淘的文章:

非块级盒子的浮动元素、绝对定位元素及块级容器(比如inline-blocks,table-cells和table-captions),以及overflow属性是visible之外任意值的块级盒子,都会创建了一个BFC。即当元素CSS属性设置了下列之一时,即可创建一个BFC:

  • float:left | right
  • position:absolute | fixed
  • display: table-cell | table-caption | inline-block
  • display: table-cell | table-caption | inline-block

然后这个独立布局的规则是这样的(摘自)

  • 在创建了 BFC的元素中,其子元素会一个接一个地放置。垂直方向上他们的起点是一个包含块的顶部,两个相邻的元素之间的垂直距离取决于 ‘margin’ 特性。在BFC中相邻的块级元素的垂直边距会折叠(collapse)。
  • 在BFC 中,每一个元素左外边与包含块的左边相接触(对于从右到左的格式化,右外边接触右边), 即使存在浮动也是如此(尽管一个元素的内容区域会由于浮动而压缩),除非这个元素也创建了一个新的BFC。

上面的定义是翻译自W3C的标准。因为自己经验有限,在这里我直接举出我找到的的BFC的经典用处,希望大家能从中体会。

在所有的文章中,被列举最频繁的例子莫过于阻止文字围绕浮动元素,请看下面代码

.item { 200px; height: 150px; border: 1px solid black;}
.pic{80px;height:80px;margin:10px;background-color:#acdae5;float:left;}
<div class="item">
	<div class="pic">picture</div>
	<p class="text">
	测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试
	</p>
</div>
picture

测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试

此时我们要做一个重要的决定!就是阻止文字围绕左侧的浮动块——那就开启右侧文字的BFC属性,用overflow:hidden

.item { 200px; height: 150px; border: 1px solid black;}
.pic{80px;height:80px;margin:10px;background-color:#acdae5;float:left;}
.text {overflow: hidden;}
picture

测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试

用法二:创建了 BFC的元素中,浮动元素参与高度的计算,这个方法也可以用于消除因为子元素浮动引起的折叠

.header {border: 1px solid black;}
.logo { 50px; height: 50px; background: blue; float: left;}
<div class="header">
   <div class="logo"></div>
</div>

 
 

这是一个很常见的问题,在网站有一个标题区域header,其中有一个logo,并且设置logo向左浮动。但问题是一旦logo向左浮动,它的包含块因为float的一些特性(后面介绍),自动折叠了起来

那么就设置包含块的BFC的属性吧,照上面可以使用overflow属性

.header {border: 1px solid black; overflow:hidden;}

 

于是成功了,并且此时当计算header的高度时,将把子元素的float元素的高度计算进来。其实解决这一类的问题应该算是清除浮动。这一类解决方案已经非常成熟了,并且一个好的方案知识点的信息量还是比较大的,可以好好介绍一下。

用处三:创建了 BFC的元素不会与它们的子元素发生外边距折叠

外边距折叠规则中有一条是,当赤裸裸的子元素(没有padding,border)与赤裸裸的父元素(还是没有padding,border)进行接触时,他们的外边距会按一定的规则进行折叠。比如

div {margin:30px 0; background: yellow;}
p {margin: 20px 0; background: blue;}
<div>
   <p></p>
</div>

This is text

看到上面因为父元素与子元素的外边距发生了折叠,父元素已经完全和子元素重合。如果元素是赤裸裸但又不想和父元素发生外边距折叠怎么办,那就给父元素设置BFC!

div {margin:30px 0; background: yellow; overflow: hidden;}

This is text

以上是三个基本用处的介绍,至少我个人对BFC还不是很理解,比如拥有不同布局的元素究竟应该如何在BFC的元素中排列等。希望有经验的同学指点一下。

参考文章

上面的文章几乎都拿BFC与haslayout属性进行对比,上面的第二篇把异同之处都整理了出来。如果是页面布局方向的同学可以深入阅读。

Position: relative

Position有四种可选值,static,relative,absolute,fixed。当你不对元素做任何position属性时,默认即为static,注意此时你无法对元素的z-index值进行设置,只有当你把position设置为出static以外的值时,z-index才生效。所以最保险的作法是设置相对定位。

CSS2.1中元素是根据三种的位置方案(positioning modes or schemes)被布局的,分别是标准流(normal flow), 浮动(float)和绝对定位(absolute position)

相对定位虽然被改动了position值,但是仍然遵循标准流的规则。CSS中难点之一在于,如何根据当前元素的位置属性判断出相邻,或者后代的位置属性,而判断出当前元素的位置属性并不难。

你可以这么理解,相对定位的元素仍然处于标准流中,只要你没有设置偏移值(top, left, right, buttom);如果你设置了偏移值,那么元素就会相对于你在标准流中的位置,依照值进行偏移。但你周围的元素会以为你还在标准流中,有一种灵魂出窍的感觉。见代码

div {  200px; height: 40px; border: 1px solid black; position: relative; }
.a {background: yellow; }
.b {background: blue; }
<div class="a"></div>
<div class="b"></div>
<div></div>
 
 
 

以上是正常情况,而下面将演示非正常情况,我将给前两个容器添加相偏移

.a {background: yellow; top: 10px; left: 10px;}
.b {background: blue; top: 10px; left: 10px;}
 
 
 

注意到虽然前两个元素虽然发生了移动,但是第三个元素并没有收到影响,并且前两个元素的移动都是根据自己的相对位移。如果这三个盒子还存在外边距的话,那么外边距的折叠效果仍然是与标准流保持一致的。

再留意上面的一个细节,这三个容器我都没有设置z-index,你也可以通过firebug之类的工具查看到它们的z-index是auto;但却出现了有叠加的情况。

元素z轴上的层叠关系不仅仅可以通过z-index这个属性来硬性规定。还可以根据浮动,负外边距,定位属性等隐式属性的隐士式体现。就上面的例子而言,当然是有position为relative的元素的优先级比static高,而同样都是relative,在代码中后书写的元素优先级又比先书写的优先级高。

z-index不在这次讲课之列,有兴趣的同学可以参考下列文章:

留意一个边界情况,如果偏移量有矛盾怎么办,比如

.a {
  left: 10px;
  right: 10px;
}

记住这样一个原则,在矛盾的情况下left的优先级要比right高,top的优先级要比bottom高,即使right书写在left后面。这一原则在绝对定位和相对于视窗定位时仍然成立。

Position: absolute

绝对定位与相对定位虽然只差一个字,但差别多了去了。首先绝对定位元素已经彻底脱离了标准流,有一种跳出三界外,不在五行中的感觉,所以它的偏移量不是相对于标准流进行偏移的,而是相对于距离它最近的position不为static的祖先元素

什么是距离它最近的position不为static的祖先元素?看下面例子

div { border: 1px solid black; }
.outer {  120px; height: 120px; position: relative;}
.inner {  80px; height: 80px; margin: 20px 0 0 20px;}
.item {  50px; height: 50px; background: red; 
	position: absolute; 
	left: 0; 
	top: 0;
}
<div class="outer">
    <div class="inner">
        <div class="item"></div>
    </div>
</div>

 

红色方块有两个父元素,为什么它绝对定位到离他更远的父元素?因为它的祖父元素(离它更远的那位)的position属性不为static,而它的父元素为static。如果想让它相对于父元素绝对定位怎么办,那就给父元素设置position不为static就行。

.inner {  80px; height: 80px; margin: 20px 0 0 20px; position:relative;}


再次强调,绝对定位就是相对于1.离它最近的父元素;2.并且这个最近父元素position值为非static。。如果元素所有的祖先元素都是static怎么办?那就相对于浏览器窗口了。

还有一个在w3c中特别注明的情况,如果父元素是行内元素怎么办?行内元素不可怕,可怕的是这个行内元素还形成了多行。看下面这个例子(你需要手动把浏览器变窄,把下面的行内元素变为多行):

.wrap { position:relative; border: 10px solid black; padding: 10px;line-height: 65px; }
.item { background: blue; position: absolute; right: 5px;bottom: 5px;} 
<span class="wrap">
    test test test test test test test test test test test test test test test test test test test test test test test test test test test
    <span class="item">This is span</span>
</span>

test test test test test test test test test test test test test test test test test test test test test test test test test test testThis is span

我让另一个蓝色行内元素相对于它父元素(同样也是行内元素)进行绝对定位,定位在右下角。

注意此时多行元素的“右”是有两个不同的值,一个是第一行的右,一个是第二行的右,并且第二行的右比第一行的右还要短,那此时相对的“右”应该是哪一个右?如图所示,当然是最后一行的。

当行内元素形成多行并且左右侧不对齐时(形成了许多的line boxes),相对于它绝对定位的元素参考的区域是,以第一行inline box的左边和上边为边界(这里的边界是内容边界,content edge而非padding edge),和最后一行inline box的右边和下边为边界的矩形区域。即使中间有某几行的右侧与最后一行没有对齐,仍然以最后一行为准。所以有了上面的结果。

上面提到了一个edge概念,在盒子模型中有content edge,padding edge,border edge,margin edge。content edge内容边距指内容(content)与内边距(padding)的交界处,或者说包围内容区域的一圈边缘,另外三个同理。

于是这里我们不得不还要考虑一个问题:比如当我指定绝对定位的left偏移量时,这个量是从这个元素的哪一个边缘到相对元素的哪一个边缘?记住了,是从该元素的外边距边缘(margin edge)到相对元素的内边距边缘(padding edge)。也就是说在绝对定位中,外边距仍然是工作中的,BTW在相对定位中也是。

阿里巴巴UED有一篇文章是关于使用absolute的一个tip,可以加深对absolute的理解: 《overflow:hidden真的失效了吗》

Position: fixed

在W3C中把fixed视为absolute的一种情况,这个值可以一句话就带过了。绝对定位不是把某个祖先元素作为参考对象吗?那fixed也是把某个祖先元素视为参考对象,只不过这个参考对象是唯一的,那就是浏览器。注意,IE6中的position是没有fixed属性的

参考文章

Containing Block

大部分时候一个元素的尺寸和位置并非是由它自己决定的(比如当它没有硬性规定长宽的时候),而是要参考它所在的那个容器,或者是它需要参考的元素容器。这个容器就叫做containing block,包含块。

包含块(containing block)的定义是我觉得w3c文档中写的最通俗易懂的,大致谈了四点:

  • 根元素(我不理解为什么没有明确指出是body或html)的包含块被称为初始包含块(initial containing block),这个包含块指的是浏览器视口(viewport),就是浏览器的窗口大小。
  • 对position为relative或static的元素来说,包含块指1.最近的;2.块级祖先元素;3.内容边缘(content edge) 所形成的区域(这一条从正面来说比较好理解,但是我还没有找到这样的一个反例)
  • position为fixed的元素,包含块就是视口
  • 如果元素是绝对定位,包含块除了要满足具有出static之外的定位属性意外,还要分一下两种情况:
    • 当满足情况的祖先元素是行内元素时,包含块应该是上一节绝对定位中最后描述的,行内元素多行时,当右侧参差不齐时形成区域的规则。在CSS2.1中,如果行内元素被分为许多行,那么包含块为undefined
    • 如果不是行内元素,包含块即是那个祖先元素内边距边缘所形成的区域。
  • 最后,如果该元素都不满足上述任何条件,那么包含块即是指“初始包含块”

参考文章

Margin Collapse

关于外边距折叠,我觉得需要了解两点

  • 什么情况下会折叠
  • 什么情况下不会折叠

关于什么情况下会折叠,还要分三种情况

相邻元素的折叠:当两个或多个元素垂直排列时(标准流中),相邻的两个外边距会重合为一个外边距。这个重合外边距的值为那两个外边距的较大值;如果为一正一负的话,则为两个的和值;如果两个都是负外边距的话,最终值为负的更厉害的那个,这个就不举例了,应该很容易理解吧。

子元素与父元素之间的折叠:在之前那个BFC中提到过这个例子,当父元素与子元素赤裸裸的接触时(都没有内边距,边框;外边距的直接接触),那么父元素的外边距会与子元素的外边距产生折叠;如何组织这个折叠?触发父元素的BFC属性;具体实例的代码在那一节也都涉及到了

自己的上边距与下边距折叠:这个就有点奇葩了,当你有一个容器,设置了上下外边距,但是content area为空,那么自己的上外边距和下外边距会进行折叠!如果此时两个外边距正好一正一负,并且刚好抵消,那么你就看不到这个盒子了!

此时再如果盒子的上方来一个赤裸裸的接触,那么又触发外边距折叠,合体了!上代码

div {margin: 20px 0;}
p {margin: 30px 0;}
<div></div>
<p></p>
 

注意上面的空白处,用浏览器工具查看一下代码,那就是效果,因为没有任何内容,所以看起来什么都没有……

那么什么情况下不会折叠?

  • 浮动元素
  • 绝对定位元素
  • display:inline-block元素
  • 触发BFC的元素
  • 被清除浮动的元素(cleared)
  • 根(root)元素

参考资料

写了这么多感觉把,感觉已经很累了,展现拉的太长了!先最后还剩三个关于书写排版的概念,这三个就真的没有接触过了,要花一段时间消化。最后还有重头戏,关于float,以及关于float和display和position的叠加。

 
 
分类: html&css

 “来到这英雄宴中的人物,就算本身武功不是甚高,见识也必广博,“太祖拳法”的精要所在,可说无人不知。乔峰一招打出,人人都是情不自禁的喝了一声采!这满堂大采之后,随即有许多人觉得不妥,这声喝采,是赞誉各人欲杀之而甘心的胡虏大敌,如何可以长敌人志气,灭自己威风?但采声已然出口,再也缩不回来,眼见乔峰第二招“河朔立威”一般的精极妙极,比之他第一招,实难分辨到底那一招更为佳妙,大厅上仍有不少人大声喝采。只是有些人憬然惊觉,自知收敛,采声便不及第一招时那么响亮,但许多“哦,哦!”“呵,呵!”的低声赞叹,钦服之忱,未必不及那大声叫好。乔峰初时和各人狠打恶斗,群雄专顾御敌,只是惧怕他的凶悍厉害,这时暂且置身事外,方始领悟到他武功中的精妙绝伦之处。但见乔峰和玄难只拆得七八招,高下已判。他二人所使的拳招,都是一般的平平无奇,但乔峰每一招都是慢了一步,任由玄难先发。玄难一出招,乔峰跟着递招,也不知是由于他年轻力壮,还是行动加倍的迅捷,每一招都是后发先至。这“太祖长拳”本身拳招只有六十四招,但每一招都是相互克制,乔峰看准了对方的拳招,然后出一招恰好克制的拳法,玄难焉得不败?”  ---------《天龙八部》

      这是天龙八部中乔峰在聚贤庄使用最基本最简单的太祖长拳大败各路群雄的描写,我每每读到此处颇有感触,其实想变成大牛,除了能够学习上乘的武功,更要修炼自身本身的内功,内功强大了,任何技能均可信手拈来,无坚不摧。

      起这个题目的原因真心不是为了黑《程序员面试宝典》,而是我觉得我的水平只能是不靠谱哪个类型的。上面这一大段废话的原因是因为最近经常被人问及去笔试面试的时候都会考些什么,其中有大部分人都拿着一本《程序员面试宝典》来寻找答案,按照我目前的水平,我还没有资格评价一本书的质量,但是我从自身亲身感受过的各种笔试面试出发,这样一本书虽然可能能够蒙骗住面试官的头两个问题,但是最终还是会暴露自己本身根本就没有练好的那一部分。每次别人问我看什么书来应对面试的时候,我总有一种说不出来的感觉,首先“应对”这个词总感觉怪怪的,不过也难怪,我们从小学开始就是“应对”各种考试,而不是怀着一种考试只是为了检验你学习的一种手段而已,学习好了,自然也考的好。不过,话说回来,在目前这样一个做什么要有个明确目的,讲究速成的文化中,这样的想法也很正常。面对这个问题,我都是用类似“比如你要准备C/C++语言你可以先看看《essential C++》,《The C programming language》这类的并不是很厚但是确实能够涵盖绝大部分知识的书,然后有时间再去看看《c++ premier》,《effective C++》这些的。"我觉得这些书本身写的就很吸引人,看起来也很快,可以指引着你思考,我觉得比看《宝典》这样的书更加心安,同时也是对内功的一次修炼。

      虽然说实习加上工作已经快有一年了,对于写程序有了新的感触,虽然说你笔试面试准备的,被问到的大部分的内容在工作中不会经常用到,但是对于写程序确实是一个思路的拓展,它可以给你提供一个解决问题的大门,指引着你不断的提高自己的程序水平,这就像内功和武功一样,只有积累的更深的内功,你才能在实际工作中得心印手,哪怕使用最简单的编程方法,你程序的效率和可读性也比别人强。

      去年笔试、面试的时候准备过一些东西,最近翻出来看看加上已经遇到很多下一届的问我如何面对面试,最近也闲来无事,加以整理,希望能够给需要的人有所帮助,我努力让我的这些信笔涂鸦可以涉及到深层一点部分,这样不会走《宝典》那种的那条其实我觉得不怎么适合的路,如果有错误的地方,欢迎指正,。

      想想我笔试、面试的时候,所遇到的题目大约涉及到如下几个方面,C\C++\C#语言本身(我没有去参加过关于Java的面试,所以这一方面我确实不知道),数据结构,算法,多线程编程,网络编程,数据库,linux,设计模式。所以,当时我大致准备的也是这几个方面的内容,那么,我先从语言本身开始吧。

 一、扑朔迷离的const     

      先从最基本简单的开始吧,const在C/C++里面作为一个声明常量的关键词,可以防止程序员在写程序的过程中不小心对这些值进行了修改。虽然在C++中有时候会用#define声明一个常量,但是这二者还是有略微的不同,比如如果用const声明的常量会进行类型检查,而#define的只是简单的替换。关于const的用法和注意的地方有很多很多,能全部弄清楚我觉得真的很牛了,我只能凭我的能力尽量列举出我已知的。

1.最常见到的一种可能被考的形式,int *const和int const *有什么区别,从我个人来讲,我最初很难记住这两个的差别,看了各种关于这方面的书都是当时能记得住,想的明白,但是过一段时间就又忘了,后来仔细思考了一下原因,我觉得是我们没有掌握着背后的原理,倘若我能够像高中推数学公式那样推出来,我才能不再被这个问题所苦恼。

int i=0,j=1;
int const ci=0;
int *pointer=&i;
int *const ic_pointer=&i;//指向的值可以被修改,不能修改指向的东西(地址)
int const *ci_pointer=&i;//指向的东西(地址)可以被修改,值不可以被修改

      从上面的代码开始分析,我们分别声明了这两种指针,注释说明了这两种指针的不同,也正如很多书中写的那样。通俗一点,可以使用*ic_pointer=1修改其指向的值,可以使用*ci_pointer=&j,修改其指向的变量,反之,则会出现编译错误。为什么会出现这样的情况呢?从最基本的开始,我们想象这些指针,变量啥的都是存在于一个连续的内存空间之中,假设如下:

     再强调一遍,这只是假设,事实上,你也可以在程序中把这些内容变量的地址全部输出出来,自己看看。从左到右编上序号0xE1-0xE6(你可以用任何你喜欢的,我用E是因为我觉得这个字母看的比较顺眼),用这个形式是为了防止后面发生混淆,那么E1这个格子里面的数值应该是0,E2的j是1,E3的ci是0,那么pointer这个格子里面的数值应该是什么呢?它所占内存里面所包含的应该是它所指向的内容的地址,也就是我们这里格子的编号,也就是E1,E4的pointer格子里面的值是E1(不是0,而就是E1这个值),同理后面两个格子也都是E1这个值。如下图所示:

     从格子E3开始,E3是个const的值,也就是说这里面的这个0是不可以更改的,这很好理解,接下来,我们查看E4格子的值,从声明看,这是一个指针,指向E1格子里面的值,E1格子里面的值是个整数型,如果把这个值改成E2,那么其指向的就是j这变量,这也很好理解。接下来进入正题,对于int *const ic_pointer这个声明,可以看到这个是一个const的指针(从*的位置,int *pointer就是一个指针,指向int型的内容),指向一个int的内容,既然const类型是不能改变该内存所存储的值,那么const指针也是不能改变该内存所存储的值,也就是我们不能把E1改成E2,也就是说其指向的地址不能被改变,但是指向的值能不能改变呢?这个和这个指针没有关系,所以,你可以通过该指针改变其指向的值。同理,让我们再来思维训练看看int const *ci_pointer,先来翻译一下,这是一个指针,指向的内容是int const,或者const int,无所谓,注意,这里面没有规定这个指针是const,只规定了其指向的值是const,虽然它不知道它指向的值是不是const,但是从它来看,它指向的值是不能更改的,也就是你不能通过我来改我指向的值,但是我格子本身的值可以随便改,比如将E1改成E2,这也就是说可以修改其指向。

     上面看了之后,可能还是有点混乱,稍微总结一下,const的关键字出现的位置决定了是哪个部分不能改变,如果出现在*之后,那么其修饰的是这个指针,如果在之前,那么其修饰的前面出现的哪个关键字。还要注意的一点,const只能管自己修饰的东西,被人修饰的它无权管辖,比如ci_pointer所指向的值i,并不是一个声明为const的值,所以就算你指向的是i,它还是可以通过i=1之类修改自己格子的值,但是你不能通过*ci_pointer修改值,因为从声明上看它认为自己是指向const int的,所以它所接到的命令是我不改掉我指向的值。

     从上面介绍的内容,如果你能想明白ci_pointer和ic_pointer哪个能指向ci,你就能想明白整个过程,也就不需要强行记忆了,虽然这可能会多花30秒,但绝对有底气的说不会错。关于指针的内容就更多了,在后面我还会再进行详细阐述的。

    另外,关于const这个关键字的准确理解方式应该是这样的:const关键字是告诉编译器该变量不能作为一个左值出现在任何表达式中,也就是将其设置为只读的(read-only)。我切身经历让我深深的体会到当你能完全理解这句话的时候,才是对const真正的理解了。

2.const在C和C++中的不同。const虽然在这两种语言都有,但是,在这两种语言中有什么不同这个问题不知道有多少人考虑过,但是我曾经也被问到过这个问题,在绝大多数情况下,确实很难发现其不同点,但是如果将程序分布到几个不同的文件中,并且使用了const,立马就能明白这两个的不同。

     假设我在file1.c中定义了一个全局变量const int i = 2;如果我想在另外一个文件file2.c使用这个值,你需额要使用extern const int i;但是如果是在C++中,你想做同样的事情,你必须在fiel1.cpp中使用如下语句extern const int i=2;为什么会这样呢?因为const标示的常量在C语言中具有的默认连接性质是external的,而在C++中是internal,这又有什么关系呢?如果下面的解释不能让你清楚的明白,那么,请先记住一点好了:在C语言中,一个const value在全部工程(程序)范围内都是可见的,而C++中const value只能在定义的这个文件中是可见的。要弄明白这个问题,首先要能够明白连接性质(linkage)到底是什么概念,又会影响到什么,关于这部分内容,我最推荐的书无疑是《c与指针》,我觉得那里面讲的很详细,在这里,为了解释这个问题,我只选择internal和external来简要说明一下。

     当你写好你的一个实现文件之后,开始编译,你的编译器会根据你的程序生成translation unit(TU)这样一个东西,先别管这事啥玩意儿,反正这是一个包含了你所有程序加上#include的那些.h的头文件里面的内容,你的整个的程序是由很多个这样的TU组成的。而internal linkage只的是所有的东西只是在这个TU中是可见的,而external linkage表示的是所有这些东西都不仅仅存在一个TU之中,也就是,可以在整个程序都是可见的。所以,在C中,在某一个文件中定义了一个const value,你可以轻松的在另外一个文件中使用它,只要使用extern关键字。但是在C++中,如果同样这样定义出一个const变量,那么在另一个文件中声明的const变量就是一个新的变量,因为在另一个文件(另一个TU)中并不存在这个变量,好在可以使用extern可以改变其连接属性,这样,就可以和在C语言中一样使用,定义该变量,另外一个可以神奇的常用的改变连接属性的关键字是static,这些展开已经大大超出了本身的内容,同样在后面也会有详细说明的。

     突然发现前面的废话太多了,显得有点长,考虑到关注兴趣和查找速度的和文章长度成反比的黄金定律,剩余部分我换到稍后的下一篇中。我会将这本不靠谱指南写下去的,争取在9、10月校园招聘期之前写完大部分,虽然能力不是很高,但是我真心想给一起需要的将要经历我曾经经历的人一些帮助,最后,再强调一遍,我真心不是要黑《程序员面试宝典》。

原文地址:https://www.cnblogs.com/Leo_wl/p/3086632.html