c语⾔helloworld代码_C语⾔基础
1. ⾸先,还是从hello world开始。
#include <stdio.h>
int main(void) {
printf("Hello Worldn");
return 0;
}
针对这个程序,要理解#include的功能。为什么要使⽤#include呢?stdio.h是什么?
stdio.h是standard input and output。这⾥因为我们使⽤了printf库函数,这个函数不是我们⾃⼰实现的,为了使⽤它,必须要在我们⾃⼰的代码中包括实现printf的代码。#include<stdio.h>的作⽤就是到系统中相应的代码,并且将其加⼊我们的代码。
问题:系统中的stdio.h在哪⾥?
2. C语⾔中的字符串
C语⾔中的字符串表⽰为内存中的字符。字符串的结尾包括NULL(0)字节。所以,“ABC”需要4个字节保存,['A','B','C','0']。确定C语⾔中字符串的长度的唯⼀的⽅法是⼀直读取内存,直到发现⼀个NULL字节。每个字符总是⼀个字节。
以上代码中,会出现字符串被截断的情况:
当在代码中写⼊字符串常量“ABC”,则该字符串常量被计算为⼀个字符指针(char *),其指向字符串的第⼀个字节。也即,下⾯代码中的指针ptr将会保存字符串“ABC”的第⼀个字符的地址。
char *ptr = "ABC"
初始化字符串的⽅法包括如下:
char *str = "ABC";
char str[] = "ABC";
char str[]={'A','B','C','0'};
数组和指针的区别后⾯还会讨论。
3. 如何声明指针?
⼀个指针指向⼀个地址。指针的类型很有⽤(很重要,它告诉编译器使⽤该指针时,每次需要读多少字节),可以如下声明指针:
int *ptr1;
在C的语法中, int * * 不应该理解成 (int *),⽽是int (*ptr)。 所以,下⾯的定义中,
int* ptr3, ptr4;
只有ptr3被声明成了指针,ptr4还是普通的int变量。如果需要声明两个int型指针,则需要如下声明:
int *ptr3, *ptr4;
4. 如何使⽤指针读/写内存?
假设声明了⼀个指针 int *ptr。假设ptr指向的地址是0x1000(32位地址和64位地址,实际上是),如果我们想利⽤这个指针进⾏写⼊操作,那么可以解引⽤:
*ptr = 0; // Writes some memory.
C编译器会检查 ptr 是int型的指针,并且要写⼊ sizeof(int)个相邻的字节,从指针所指的起始位置开始。譬如,在以上的例⼦中,将会写⼊0x1000,0x1001,0x1002,0x1003四个字节,这四个字节的值都为0。对基础数据类型,这都⼀样,对结构体则稍微复杂⼀些。
5. 指针运算
对指针可以进⾏运算,譬如可以对指针进⾏加减操作。 但是,加减操作的含义需要认真思考,因为加减操作的单位依赖于指针的类型。对于 char 指针,⽐较简单,因为它的单位是⼀个字节:
char *ptr = "Hello"; // ptr 保持 'H'的内存位置
ptr += 2; //ptr 现在指向第⼀个'l'
但是如果指针是多字节的其他类型,那么操作就复杂了:
char *ptr = "ABCDEFGH"; // ptr是char类型的指针
int *bna = (int *) ptr; // bna是 int型的指针,同样指向上⾯字符串的位置
bna +=1; // 对bna加1;这⾥,因为bna是int型,所以 +1 实际上是 增加了4个字节
ptr = (char *) bna; //将ptr指向 bna 现在指向的位置
printf("%s", ptr); //此时打印,只会打印出EFGH,因为⼀个字符只占⼀个字节,⽽bna向前⾛了4个字节
return 0;
结果如下:
因为指针的运算依赖于指针指向的类型,所以不能对void类型的指针进⾏运算操作。
可以如下理解指针运算的操作:
int *offset = ptr1 + 3;
以上代码的分解动作如下:
int *ptr1 = ...;
char *temp_ptr1 = (char*) ptr1;
int *offset = (int*)(temp_ptr1 + sizeof(int)*3);
6. void指针
void指针就是没有类型的指针。void指针⽤于⽆法确定数据类型,以及C代码正在与其他编程语⾔兼容的情况。可以认为它只是⼀个内存地址。不能直接读写该指针,因为它没有类型,从⽽也就不能确定读写的⼤⼩。譬如:
void *give_me_space = malloc(10);
char *string = give_me_space;
以上代码不需要强制类型转换,因为C⾃动将void*转换为合适的类型。
gcc和clang没有遵循ISO-C标准,这两个编译器使得可以进⾏void *类型的运算。但是,考虑到代码的兼容性,最好不要这样做。
7. printf调⽤write 还是write调⽤printf?
这个问题实际上讨论的是库函数和系统调⽤的区别。库函数是应⽤层的概念,⽽系统调⽤涉及到操作系统的系统层⾯,在内核中实现。所以,printf调⽤write来实现打印功能。另⼀⽅⾯,printf包括了⼀个内部的缓冲区,因此,可以通过避免每次调⽤Printf时都得调⽤write来提⾼性能。因为系统调⽤涉及到⽤户态和内核态的转换,开销⽐较⼤。
8. 指针的值如何打印?整型以及字符串?
打印指针时使⽤"%p",整型"%d",“%s”打印字符串。完整的列表。
int *ptr = (int *) malloc(sizeof(int)); //在堆上分配空间
*ptr = 10;
printf("%pn", ptr); //打印由ptr指向的地址
printf("%pn", &ptr); //打印ptr本⾝的地址
printf("%d", *ptr); //打印ptr指向的地址中的值(10)
return 0;
看⼀下实例:
以及打印的结果:
对于字符型,参考下⾯的例⼦:
char *str = (char *) malloc(256 * sizeof(char));
strcpy(str, "Hello there!");
printf("%pn", str); // 打印地址
printf("%s", str); //打印值
return 0;
9. 指针和数组的区别是什么?举出⼀些它们不能混⽤的例⼦
指针和数组都可以⽤来声明字符串常量。譬如:
char ary[] = "Hello";
char *ptr = "Hello";
ary都指向的是数组的第⼀个字节,也都能打印出来它们:
char ary[] = "Hello";
char *ptr = "Hello";
// Print out address and contents
printf("%p : %sn", ary, ary);
printf("%p : %sn", ptr, ptr);
⼀⽅⾯,ary的内容是可以修改的,但是注意不要超出array本⾝的⼤⼩;⽽ptr指向的则是只读内存,也即,它指向的是静态的字符串常量,所以不能改变ptr指向的内容:
strcpy(ary, "World"); // OK
strcpy(ptr, "World"); // NOT OK - Segmentation fault (crashes)
另⼀⽅⾯,ary本⾝是不能修改的,也即ary作为指针,只能指向原始的数组的位置;⽽ptr⽐较灵活,可以修改,指向内存的另⼀块地址:
ptr = "World"; // OK!
ptr = ary; // OK!
ary = (..anything..) ; // WONT COMPILE
// ary is doomed to always refer to the original array.
printf("%p : %sn", ptr, ptr);
strcpy(ptr, "World"); // OK because now ptr is pointing to mutable memory (the array)
10. sizeof对ary和ptr返回的值是多少?
sizeof(ary), ary就是数组。返回的值是存储该数组所需的字节,譬如上⾯的
char ary[] = "Hello";
返回的就是6。
sizeof(char *),返回的是指针的⼤⼩,因为指针本⾝需要存储的是⼀个地址,因此,在32位系统上,指针的⼤⼩是4个字节;⽽64位系统
sizeof是⼀个很特殊的操作符。在编译程序之前,编译器会将它替换掉,因为在编译时,这些值就能确定。
char str1[] = "will be 11";
char* str2 = "will be 8";
sizeof(str1) //11 because it is an array
sizeof(str2) //8 because it is a pointer
所以,sizeof在编译时就被优化掉了。
基础c语言代码11. 进⼀步理解指针和数组
以下两段代码,哪⼀段是正确的?
int* f1(int *p) {
*p = 42;
return p;
} // This code is correct;
以及
char* f2() {
char p[] = "Hello";
return p;
} // Incorrect!
原因是,数组保存的字符串保存在栈上,因此,当函数返回时,栈已经撤销,地址⽆效。⽽指针指向的常量⼀般保存在静态值区域,⼀直保留。下⾯的代码可以正确:
char* f4() {
static char p[] = "Hello";
return p;
} // OK
此时,明确指定数组中存放的值是static的,因此,它在程序⽣命期间都有效,这个地址⼀直是有效的。
static的理解可以参照这篇。
12. 字符串拷贝
下⾯这段代码的问题?
void mystrcpy(char*dest, char* src) {
// void means no return value
while( *src ) { dest = src; src ++; dest++; }
}
上⾯的代码中,仅仅是将dest指向了src,因此,dest指向的内存处得内容并没有发⽣变化。同时,上⾯代码也没有拷贝null字节。
改进代码如下:
while( *src ) { *dest = *src; src ++; dest++; }
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论