- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
大家好呀,今天丸丸给大家带来的是《C和指针》系列第二弹——读书笔记硬核分享系列,希望大家能够多多支持呀!都是干货,感兴趣的话大家就收藏一下吧!大家的支持是我更新这一系列最大的动力!
C语言中不存在专门的“赋值语句”,在C语言中,赋值是一种操作,就像加法和减法一样,所以赋值就在表达式内进行。
只要在表达式后面加上一个分号,就可以把表达式转变为语句。所以,下面这两个表达式
x = y + 3;
ch = getchar();
实际上是表达式语句,而不是赋值语句。所以,下面的操作也是合法的:
y + 3;
getchar();
当这些语句在执行时,表达式被求值。但是它们的结果并不保存于任何地方,因为它们并未使用赋值操作符。
代码块可以用于要求出现语句的地方。它允许你在语法要求中只出现一条语句的地方使用多条语句。代码块还允许让数据的声明非常靠近使用它的地方。
注意:代码块是否具有返回值要分具体的情况,比如代码块中只有一个表达式,或者它的最终的值是一个数字的话,那么他就具有返回值,可以将其作为右值赋值给变量,如果里面是一个带有分号的语句,那么这个代码块就不具有右值的性质。
注意:下面的这种情况是可行的:
C int n = 10; int a = {n};
下面的程序是可以正常运行的,因为{n}
也是具有右值的性质,虽然这个值是在运行的时候确定的。
if语句的语法结构如下所示:
if(expression)
statement;
else
statement;
括号是if语句的一部分,而不是表达式的一部分,因此它是必须出现的,即使是那些极为简单的表达式也是如此。
注意:switch后面的括号中的语句会实际执行,如果执行了自增操作、自减操作或者赋值操作就会对相应的变量产生副作用并发生改变。
注意:在switch语句中,continue语句没有任何的效果,如果强行使用,编译器会报错。
注意:包含这些标签的goto语句可以出现在同一个函数的任何位置,即goto语句不能跨函数。
答:
if(condition)
;
else
{
statements
}
可以对条件进行修改,省略空的then语句。它们的效果是一样的:
if(!(condition))
{
statements
}
警告:标准规定无符号值执行的所有移位操作都是逻辑移位,但对于有符号值,到底是采用逻辑移位还是算术移位则取决于编译器。因此,一个程序如果使用了有符号数的移位操作,它就是不可移植的。
警告:在下面的语句中,认为a和x被赋予相同的值的说法是不正确的:
a = x = y + 3;
如果x是一个字符性变量,那么y+3的值就会被截去一段,以便容纳于字符类型的变量中。那么a所赋得值就是这个被截短后得值。
在下面得这个常见得错误中,这种截短正是问题的根源所在:
char ch;
···
while((ch = getchar())!=EOF) ···
EOF需要的位数闭字符型值所能提供的位数要多,这也是getchar返回 一个整型值而不是字符值的原因。然后,把getchar得返回值首先存储于ch中将导致它被截短。然后这个被截短后得值被提升为整型并与EOF进行比较。当这段存在错误得代码在使用有符号字符集的机器上运行时,如果读取了一个值为\377的字节,循环将会终止,因为这个值截短再提升之后与EOF相等。当这段代码在是以哦那个无符号字符集的机器上运行时,这个循环将永远都不会终止!
警告:a = a+5和a+=5之间在现代编译器上并没有太大的区别,而且现代的编译器为这两种表达式产生优化代码并无多大问题,但请考虑下面两条语句,如果函数f没有副作用,它们是相同的。
a[2*(y - 6*f(x))] = a[2*(y - 6*f(x))] + 1;
a[2*(y - 6*f(x))] += 1;
在第一种形式中,用于选择增值位置的表达式必须书写两次,一次在赋值号的左边,另一次在赋值号的右边。由于编译器无从知道函数h是否具有副作用,所以它必须两次计算下标表达式的值。第二种形式效率更高,因为下标只计算一次。
sizeof操作数既可以是个表达式(常常是单个变量),也可以是两边加上括号的类型名。这里有个例子:
sizeof(int);
sizeof x;//x是一个变量
下面的写法也是合法的:
sizeof(x);
这是因为括号在表达式中总是合法的。
在判断表达式的长度时,并不需要对表达式进行求值,所以sizeof(a = b+1)并没有向a赋任何值。
a = get_value();
count_value(a);
while(a > 0)
{
···
a = get_value();
count_value(a);
}
上面的代码我们可以写成其它更为简洁的形式:
while(a = get_value(),count_value(a),a>0)
{
···
}
也可以使用内嵌的赋值形式,如下所示:
while(count_value(a = get_value()),a > 0)
{
···
}
现在,循环中用于获得下一个值得语句只需要出现一次。逗号操作符使源程序更易于维护。如果用于获得下一个值得方法在将来需要改变,那么代码中只有一个地方需要修改。
这里还有一个技巧:
while(x<10)
b+= x,
x+= 1;
在这个例子中,逗号操作符把两条赋值语句整合成一条语句,从而避免了在它们得两端加上花括号。不过,这并不是一个好做法,因为逗号和分号得区别过于细微,人们很难注意到第一个赋值后面是一个逗号而不是分号。
左值就是那些可以出现在赋值符号左边得东西。右值就是那些可以出现在赋值符号右边得东西。
下面是一个例子:
a = b + 25;
a是一个左值,因为它标识了一个可以存储结果得值得地点;b+25是一个右值,因为它指定了一个值。
它们可以互换吗?
b + 25 = a;
原先用作左值得a此时也可以当作右值,因为每个位置都包含一个值。然而,b+25不能作为左值,因为它并未标识一个特定得位置。因此,这条赋值语句是非法的。
注意:当计算机计算b+25时,它的结果必然保存于机器的某个地方,也许是寄存器中,也许是内存中的栈上。但是,程序员并没有办法去预测该结果会存储在什么地方,也无法保证这个表达式的值下次还会存储于同一个地方。其结果是,这个表达式的不是一个左值。基于同样的理由,字面值也都不是左值。
听上去似乎变量是可以作为左值而表达式不能作为左值,但这个推断并不准确。在下面的赋值语句中,左值便是一个表达式:
int a[30];
···
a[ b + 10 ] = 0;
**下标引用实际上是一个操作符,所以表达式的左边实际上是个表达式,但它却是一个合法的左值,因为它标识了一个特定的位置,我们以后可以在程序中引用它。**这里还有一个例子:
int a,*pi;
···
pi = &a;
*pi = 20;
看第二条赋值语句,它左边的那个值显然是一个表达式,但它确实一个合法的左值。为什么?指针pi的值是内存中某个特定位置的地址,*操作符使机器指向那个位置。当它作为左值使用时,这个表达式指向需要进行修改的位置;当它作为右值使用时,它就提取当前存储于这个位置的值。
有些操作符,如间接访问和下标引用,它们的结果是个左值。其余操作符的结果则是个右值。
下面这个代码段包含了一个潜在的问题:
int a = 5000;
int b = 25;
long c = a*b;
即表达式a*b的是以整型进行计算,在32位整数的机器上,这段代码运行起来毫无问题,但在16位整数的机器上,这个乘法运算会产生溢出,这样c就会被初始化位错误的值。
解决方案是在执行乘法运算之前把其中一个(或两个)操作数转换为长整型:
long c = (long)a*b;
名字与内存位置之间的关联并不是硬件来提供的,而是由编译器为我们实现的。所有这些变量给了我们一种更为方便的方法记住地址——硬件仍然通过地址访问内存位置。
不能简单的通过检查一个值的位来判定它的类型。值得类型并非值本身所固有得一种特性,而是取决于它的使用方式。
下面介绍一些有关指针表达式作为左值和右值时分别代表什么
char ch = 'a';
char *cp = &ch;
表达式 | 左值 | 右值 |
---|---|---|
ch | 合法左值。变量ch这个内存的地址,表示的是这块地址空间 | ‘a’ |
&ch | 非法左值。(当表达式&ch进行求值时,它的结果应该存储于计算机中的什么地方我们并不知道,即这个表达式并未表示任何机器内存的特定位置,所以它不是一个合法的左值) | 变量ch的地址 |
cp | 合法左值。变量cp所处的内存位置,表示的是这块地址空间 | ch的地址 |
&cp | 非法左值。(原因与&ch类似,不过这次所取得是指针变量的地址) | cp的地址 |
*cp+1 | 非法左值。(这个表达式的最终结果的存储位置并未清晰定义,所以它不是一个合法的左值) | ‘b’ |
*(cp+1) | 合法左值。cp+1表示的是变量ch所处的内存空间的下一块字符型空间的地址,对其解引用表示的就是这块空间 | cp+1表示的是变量ch所处内存空间的下一块字符型空间的地址,对其解引用就是表示这块空间中存储的值 |
++cp | 非法左值。表达式的结果是增值后的指针的一份拷贝,因为前缀++先增加它的操作数的值再返回这个结果。这份副本的存储位置并未清晰定义,所以它不是一个合法的左值。 | ch的地址(cp的值)+1(加1是因为ch是字符型) |
cp++ | 非法左值。后缀++同样增加cp的值,但它先返回cp的值的一份拷贝然后再增加cp的值。这样,这个表达式的值就是cp原来的值的一份副本。 | ch的地址(cp的值) |
*++cp | 合法左值。表示变量ch所处内存空间的下一块字符型空间(和*(cp+1)是等效的) | 变量ch所处内存空间的下一块字符型空间中存储的值(和*(cp+1)是等效的) |
*cp++ | ch的存储位置(三个步骤:操作符产生cp的一份副本;然后操作符增加cp的值;最后,在cp的副本上执行间接访问操作) | ch的值 |
++*p | 非法 | cp所指向位置的值增加1,即’b’。(由于这两个操作符的结合性都是从右向左,因此首先执行的是间接访问操作,然后cp所指向的位置的值增加1,表达式的结果是这个增值后的值的一份副本) |
(*cp)++ | 非法 | ch的值 |
++*++cp | 非法 | 首先对cp进行操作,然后取地址得到变量ch后面空间中存放的值,然后对这个值进行操作,最终得到的是变量ch后面空间中存放的值+1 |
*cp | 非法 | ch的值+1即’b’ |
让指针指向数组最后一个元素后面的那个位置是合法的,但是对这个指针执行间接访问可能会失败。
下面是一个例子:
#define N_VALUES 5
float value[N_VALUES];
float *vp;
for(vp = &value[0]; vp < &values[N_VALUES]; )
*vp++ = 0;
&values[N_VALUES]
表示数组最后一个元素后面那个内存位置的地址。当vp到达这个值时,我们就知道到达了数组的末尾,故循环终止。
这个例子中的指针最后指向的是数组最后一个元素后面的那个内存位置。指针可以合法的获得这个值,但对它执行间接访问时,将可能意外的访问原先存储于这个位置的变量。程序员一般无法知道那个位置原先存储的是什么变量。因此,在这种情况下,一般不允许对指向这个位置的指针执行间接访问操作。
同上面的代码。for语句用了一个关系测试来决定是否结束循环。这个测试是合法的,因为vp和指针常量都指向同一数组中的元素(事实上,这个指针常量所指向的是数组最后一个元素后买你的那个内存位置,虽然在最后一次比较时,vp也指向了这个位置,但由于此时未对vp执行间接访问操作,因此它是安全的)。使用!=操作符代替<操作符也是可行的,因为如果vp未到达它的最后一个值,这个表达式的结果总是假的。
现在考虑下面这个循环:
for(vp = &values[N_VALUES]; vp > &values[0]; )
{
*--vp = 0;
}
它和前面那个循环所执行的任务相同,但数组元素将以相反的次序清除。我们让,vp执行数组最后个元素后面的内存位置,但在对它进行间接访问之前先执行自减操作。当vp指向数组第一个元素时,循环便终止,不过这发生在第一个数组元素被清除之后。
有些人会对上面的循环进行如下改进:
for(vp = &values[N_VALUES]; vp >= &values[0]; vp--)
{
*vp = 0;
}
现在vp指向数组最后一个元素,它的自减操作放在for语句的调整部分进行。这个循环将存在一个问题:
警告:在数组的第一个元素被清除后,vp的值还将减去1,而接下来的一次比较运算是用于结束循环的,但这就是问题所在:比较表达式vp>=&values[0]
的值是未定义的,因为vp移到了数组的边界之外。标准允许指向数组元素的指针指向数组的最后一个元素后面的那个内存位置的指针进行比较,但不允许与指向数组第一个元素之前的那个内存位置的指针进行比较。
实际上,在绝大多数编译器中,这个循环将顺利完成任务。然而还是应该避免使用它,因为标准并不保证它可行。
答:机器无法判断。编译器根据值得声明类型后创建适当得指令,机器只是盲目得执行这些指令而已。
答:有两个错误。对增值后得指针进行解引用,数组的第一个元素没有被清零。另外,若指针在越过数组的右边界以后仍然进行解引用,它将把其它内存地址的内容清零。
我使用 4 核 i7 CPU(8 个逻辑核),debian linux 虚拟机也是 debian linux 程序使用 gcc 编译,没有进行特殊优化(默认编译设置) 我循环了该程序 1000 次。当
我有一个实时 linux 桌面应用程序(用 C 语言编写),我们正在移植到 ARM(4 核 Cortex v8-A72 CPU)。在架构上,它结合了高优先级显式 pthread(其中 6 个)和一对
我已经在 Digital Ocean ubuntu 实例上安装了 Solr 6: install_solr_service.sh solr-6.1.0.tgz 并验证 Solr 正在运行。但是,我无法
关闭。这个问题需要更多focused .它目前不接受答案。 想改进这个问题吗? 更新问题,使其只关注一个问题 editing this post . 关闭 3 年前。 Improve this qu
关闭。这个问题需要更多focused .它目前不接受答案。 想改善这个问题吗?更新问题,使其仅关注一个问题 editing this post . 6年前关闭。 Improve this questi
我有一个大约 2000 维的特征向量。都是直方图特征。 我不知道在我的情况下哪个有效:将 SVM 与 RBF 核或卡方核应用? 你能建议我在我的情况下有效的内核吗? 最佳答案 一般来说,卡方和交集内核
我们有一台 12 核 MacPro 来进行一些蒙特卡罗计算。其 Intel Xeon 处理器启用了超线程 (HT),因此实际上应该有 24 个进程并行运行才能充分利用它们。然而,我们的计算在 12x1
所以这段代码: library(plyr) library(doMC) registerDoMC(cores=2) x=1:100 llply(x, some_function, .parallel=
Netty Server 流到 Netty 客户端(点对点,1 对 1): 好的 案例:Server和Client都是12 cores , 1Gbit NIC => 以每秒 300K 200 字节消息
我对以下 C# 代码的线程激活顺序感到困惑。它创建了 10 个线程,随机启动它们,每个线程模拟执行一个耗时的工作 10 次,如果你检查调试输出,线程似乎不是随机选择的,请看下面的输出示例,注意线程 #
这是我考试时提出的问题。我给出了以下答案,我的得分是0分。教授甚至不同意给予任何部分的认可,也没有告诉我我的答案有什么问题。谁能帮我找出我的答案有什么问题吗? 这是我在考试中给出的答案。缺点是:1)
我有一个 Intel Xeon E5-2620,它有 24 个 CPU。我写了一个应用程序,它创建 24 个线程来使用 openssl 解密 AES。当我在 100 万数据解密时将线程数从 1 增加到
我正在开发一个在图层上绘画的应用程序。这是一个示例代码,展示了我的绘画方式。 UIImageView * currentLayer = // getting the right layer... UI
在带有 2Tb DRAM 的 80 核 (160HT) nehalem 架构上运行一些测试后,我遇到了一个小的 HPC 问题: 当每个线程开始请求有关“错误”套接字上的对象的信息时,具有 2 个以上套
由于潜在的性能问题,我刚刚将测试实例从小型“标准”(1 核,1.75GB RAM)实例升级到中型“标准”实例(2 核,3.5GB RAM),这似乎是快速击中。我们确实存在应用程序池回收和必须重新预热某
我知道,为了在 Android 中保持响应式界面,繁重的工作必须在独立线程中完成。我很清楚如何实现这一点(通过使用 AsynTask...等),这不是问题的重点,只是让每个人都知道。 但是我已经在一个
我写了一个简单的多线程 Java 应用程序,主要方法只创建 5k 个线程,每个线程将循环处理一个包含 5M 条记录的列表。 我的机器规范: CPU 内核:12 个内核 内存:13Gb RAM 操作系统
让我们假设我们有固定数量的计算工作,没有阻塞、 sleep 、I/O 等待。工作可以很好地并行化——它由 100M 小而独立的计算任务组成。 什么是 4 核 CPU 的速度更快 - 运行 4 个线程或
我正在使用 WEKA/LibSVM 来训练术语提取系统的分类器。我的数据不是线性可分的,因此我使用 RBF 内核而不是线性内核。 我关注了guide from Hsu et al.并迭代 c 和 ga
谁能告诉我为什么在具有四个 ARMv7 处理器的 Jetson TK1 上调用 Python 的 multiprocessing.cpu_count() 函数会返回 1? >>> import mul
我是一名优秀的程序员,十分优秀!