C语言,你真的弄懂了么?

C代码 复制代码 收藏代码
  1. #include<stdio.h>
  2. int main(void) {
  3. int x[4];
  4. printf("%p\n",(void*) (x));
  5. printf("%p\n",(void*) (x +1));
  6. printf("%p\n",(void*)(&x));
  7. printf("%p\n",(void*)(&x + 1));
  8. }

假设x的地址为n,那么输出为:

答案代码 复制代码 收藏代码
  1. n
  2. n+4
  3. n
  4. n+16

window下使用gcc编译输出结果:

程序输出结果代码 复制代码 收藏代码
  1. 0x22cd44
  2. 0x22cd48
  3. 0x22cd44
  4. 0x22cd54

前三个还比较好理解,最后一行中&x实际表示是一个类型为int(*)[4]类型的指针,所以&x+1后地址增加16。

有一个和上面类似的程序(源自《C语言深度剖析》,可以在网上搜索下载到):

C代码 复制代码 收藏代码
  1. #include<stdio.h>
  2. int main(void)
  3. {
  4. int a[] ={1,2,3,4,5};
  5. int *p =(int*)(&a+1);
  6. printf("%d%d\n",*(a+1),*(p-1));
  7. return 0;
  8. }

此程序输出结果为2,5

类似的还有个程序:

C代码 复制代码 收藏代码
  1. #include<stdio.h>
  2. int main(void)
  3. {
  4. int a[] ={1,2,3,4,5,6,7,8,9,10};
  5. int (*p1)[3] =&a;
  6. int (*p2)[4] =a;
  7. printf("%d%d\n",*(*(p1+1)+1),*(*(p2+2)+1));
  8. return 0;
  9. }

该程序编译时会提示警告(p1和p2初始化采用不兼容的指针类型)。

这里,p1和p2都是数组指针。p1指向有三个元素的整型数组,p2指向有四个元素的整形数组。

数组指针p1类似于一个二维数组b[][3],而根据二维数组b[i][j]可以表示成*(*(b+i)+j)的形式。所以*(*(p1+1)+1)相当于二维数组b[][3]中的b[1][1],因而对应于a[4],所以输出结果为5,类似的可以得出另一个指针的输出结果。

所以整个输出结果为5,10

同样,还有个程序:

C代码 复制代码 收藏代码
  1. #include<stdio.h>
  2. int main(void)
  3. {
  4. int a[4] ={1,2,3,4};
  5. int *ptr1 =(int*)(&a+1);
  6. int *ptr2 =(int *)((int)a+1);
  7. printf("%x,%x\n",ptr1[-1],*ptr2);
  8. return 0;
  9. }

ptr1[-1]的值(跟上面的例子的情况类似)为4

而*ptr2的值则根据处理器的不同而可能有不同的结果(参见大端模式和小端模式endianness

如果为小端模式(例如intelx86兼容处理器,8051,avr等),那么*ptr2(注意使用题目中使用了%x输出格式)输出结果为2000000

如果为大端模式(例如motorola 68k,powerpc,IBM sys/360等),那么*ptr2输出结果为100

判断处理器使用什么模式,可以使用下面函数进行检测(相关链接 ):

C代码 复制代码 收藏代码

  1. intCheckEndian()
  2. {
  3. union{
  4. int i;
  5. char c;
  6. }e;
  7. e.i = 1;
  8. return (e.c ==1);
  9. }

如果使用大端模式,那么数组a在内存中表示(十六进制)为 00 00 00 01 00 00 00 02 00 00 00 0300 00 00 04

ptr2指向第二个00,所以为00 00 01 00(即0x100)

如果是小端模式,那么数组a在内存中表示为01 00 00 00 02 00 00 00 03 00 00 00 04 00 0000,ptr2指向第一个00,所以其指向内容为00 00 0002,由于使用了小端模式,所以需要颠倒过来表示即02000000(也即0x2000000).

接着是一些二维数组和二级指针的一些例子(同样源自《c语言深度剖析》):

C代码 复制代码 收藏代码
  1. #include<stdio.h>
  2. int main(void)
  3. {
  4. inta[3][2]={(1,2),(3,4),(5,6)};
  5. int *p;
  6. p = a[0];
  7. printf("%d\n",p[0]);
  8. return 0;
  9. }

编译后运行,结果为2,因为圆括号内的使用了逗号表达式,二维数组a的初始化相当于int a[3][2]={2,4,6};

C代码 复制代码 收藏代码
  1. #include<stdio.h>
  2. int main(void)
  3. {
  4. int a[5][5];
  5. int (*p)[4];
  6. p = a;
  7. printf("a_ptr=%#p,p_ptr=%#p\n",&a[4][2],&p[4,2]);
  8. printf("%p,%d\n",&p[4][2]-&a[4][2],&p[4][2]-&a[4][2]);
  9. return 0;
  10. }

编译后运行,结果为

运行结果代码 复制代码 收藏代码
  1. a_ptr=0X0022FF70,p_ptr=0X0022FF38
  2. FFFFFFFC,-4

这是因为二维数组实际上仍然用一维数组来表示。而int(*p)[4]相当于把a的一维数组表示又转化成二维数组[][4],这样&p[4][2]相当于p+4*4+2,&a[4][2]相当于p+4*5+2,所以二者相减结果为-4.

接着是几个内存分配的程序(源自《高质量程序设计指南--c++/c语言》)

(1)

C代码 复制代码 收藏代码
  1. #include<stdio.h>
  2. voidgetmemory(char *p)
  3. {
  4. p = (char*)malloc(100*sizeof(*p));
  5. }
  6. int main(void)
  7. {
  8. char *str =NULL;
  9. getmemory(str);
  10. strcpy(str,"hello,world");
  11. printf("%s\n",str);
  12. return 0;
  13. }

编译运行后程序会发生崩溃,因为getmemory只是将NULL值传给参数p,然后又给p分配了100个字节空间,对str没有任何改变。由于str仍未NULL,所以对空串进行串拷贝会发生崩溃。

(2)

C代码 复制代码 收藏代码
  1. #include<stdio.h>
  2. char*getmemory(void)
  3. {
  4. char p[] ="hello,world";
  5. return p;
  6. }
  7. int main(void)
  8. {
  9. char *str =NULL;
  10. str = getmemory();
  11. printf("%s\n",str);
  12. return 0;
  13. }

编译运行该程序,其输出结果为乱码。

这是因为C语言中栈帧布局可知,getmemory被调用后,会在栈上分配数组p来存放"hello,world"字符串并返回该串地址,但是在getmemory返回后,在栈上分配的数组部分已经变成无效状态,此时调用printf函数,就会覆盖掉原来数组p中的内容。但是输出串地址仍是以前的地址,所以可能输出乱码。

(3)

C代码 复制代码 收藏代码
  1. #include<stdio.h>
  2. voidgetmemory(char **p,int num)
  3. {
  4. *p = (char*)malloc(num);
  5. }
  6. int main(void)
  7. {
  8. char *str =NULL;
  9. getmemory(&str,100);
  10. strcpy(str,"hello,world");
  11. printf("%s\n",str);
  12. return 0;
  13. }

编译运行该代码会输出”hello,world",但是该程序没有将分配空间释放,所以可能会产生内存泄漏

(4)

C代码 复制代码 收藏代码
  1. #include<stdio.h>
  2. voidtest(void)
  3. {
  4. char *str =(char*)malloc(100);
  5. strcpy(str,"hello");
  6. free(str);
  7. if(str != NULL)
  8. {
  9. strcpy(str,"world");
  10. printf("%s\n",str);
  11. }
  12. }
  13. int main(void)
  14. {
  15. test();
  16. return 0;
  17. }

编译运行该程序后,可能产生非常危险的后果。因为前面给str分配空间并释放,但是并没有将str设置为NULL,因而str成为“野指针”,下面还要继续对str原来位置复制一个串"world"并输出,这就成了篡改堆中内容,可能带来非常严重的后果。

接下来是一个要求不用循环和条件语句输出1到1000的所有整数(来源)。

(方法1):该方法会产生一个错误(除0故障),但能输出正确结果

C代码 复制代码 收藏代码
  1. #include<stdio.h>
  2. #define MAX 1000
  3. int boom;
  4. int foo(n) {
  5. boom = 1 / (MAX-n+1);
  6. printf("%d\n",n);
  7. foo(n+1);
  8. }
  9. int main() {
  10. foo(1);
  11. }

(方法二):

C代码 复制代码 收藏代码
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. void f(int j)
  4. {
  5. static void (*const ft[2])(int) = { f, exit};
  6. printf("%d\n",j);
  7. ft[j/1000](j + 1);
  8. }
  9. int main(int argc,char *argv[])
  10. {
  11. f(1);
  12. }

这段代码可以简化为:

C代码 复制代码 收藏代码
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. voidmain(int j) {
  4. printf("%d\n",j);
  5. (&main + (&exit -&main)*(j/1000))(j+1);
  6. }

运行此程序时,由于不带任何参数,所以j的初始值为1(相当于argc参数,只是这里变量名换一下而已,不影响程序的执行),然后输出1,下一句中j/1000值为0(j为1-999之间任意整数时其值均为0),所以相当于执行(&main)(2),这是一个函数指针调用,然后输出2,继续执行下去直至j为999时,会调用(&main)(1000),此时输出1000,j/1000值变为1,所以下一步调用(&main+(&exit-&main))(1001),即exit(1001),此时使用exit跳出函数的执行。

不使用中间变量交换两个整型变量的值,代码如下:

C代码 复制代码 收藏代码
  1. int x,y;
  2. x=x^y;
  3. y=x^y;
  4. x=x^y;

Duff'sdevice :

C代码 复制代码 收藏代码
  1. send(to, from, count)
  2. register short *to,*from;
  3. register count;
  4. {
  5. registern=(count+7)/8;
  6. switch(count%8){
  7. case 0: do{ *to = *from++;
  8. case 7: *to =*from++;
  9. case 6: *to =*from++;
  10. case 5: *to =*from++;
  11. case 4: *to =*from++;
  12. case 3: *to =*from++;
  13. case 2: *to =*from++;
  14. case 1: *to =*from++;
  15. }while(--n>0);
  16. }
  17. }

这个代码格式是老的代码格式。具体讲解见上面的链接。

检查一个字符串(名称为s1)是否是另外一个字符串(名称为s2)的旋转版本(来源)。例如"stackoverflow"的旋转版本字符串有: "tackoverflows",“overflowstack"等。

算法实现方法如下:

(1)确定两个串长度相等

(2)将s1和s1连接起来,检查s2是否是连接后的串的字串

C代码 复制代码 收藏代码
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<string.h>
  4. intIsRotation(chars1[], char s2[])
  5. {
  6. int len1 =strlen(s1),len2=strlen(s2);
  7. char *str =malloc((len1+len1+1)*sizeof(char));
  8. if(len1 != len2)
  9. return 0;
  10. if(str == NULL)
  11. {
  12. fprintf(stderr,"error whileallocating space\n");
  13. return -1;
  14. }
  15. if(strcpy(str,s1) == NULL ||strcat(str,s1) == NULL)
  16. {
  17. fprintf(stderr,"error whilecopying or concatenate string s1 tostr\n");
  18. return -1;
  19. }
  20. if(strstr(str,s2) !=NULL)
  21. return 1;
  22. return 0;
  23. }

这段代码是我自己写的,可能有不完善的地方。当执行中出错时返回-1,如果是旋转串则返回1,否则返回0

深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值