Open solomonxie opened 6 years ago
格式:
type arrayName [ arraySize ];
示例:
// 声明数组 (只创建指定大小的内存空间,不赋值)
double balance[5];
// 数组初始化并赋值 (注意是{} 符号)
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
// 或者,自动识别数组中项目数量
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};
// 为数组的某一项赋值
balance[4] = 50.0;
// 读取数组某一项元素
double salary = balance[2];
多维数组:
int a[3][4] = {
{0, 1, 2, 3} , /* 初始化索引号为 0 的行 */
{4, 5, 6, 7} , /* 初始化索引号为 1 的行 */
{8, 9, 10, 11} /* 初始化索引号为 2 的行 */
};
// 或者,省略{}括号,根据纬度自动填充
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
// 访问多维数组
int val = a[2][3];
C语言中,字符串实际上是一个特殊的数组
,即每一个字符为数组中的一个元素,且数组的最后一个元素必须是'\0'
。
如以下二者是完全想等的:
// 按数组形式直接声明字符串:
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
// 更简单的方式声明字符串:
char greeting[] = "Hello";
那么,无论用以上哪种方式创建一个数组,内存中的结构都是一样的:
理解枚举类型
:
C中的Enumeration是一个常量集合
,相当于Python的Dict字典变量,即key-value
数组。
在Python中,定义一个字典是:
WEEK = {
"MON": 1,
"TUE": 2,
"WED": 3
#....
}
而在C中,定义一个“字典”是:
enum WEEK {
MON = 1,
TUE = 2,
WED, THU, FRI, SAT, SUN
}
C中的“字典”特殊的是,如果后面的keys
不赋值,那么会自动在前面一个值上+1。如果前面没有项,那么自动变为0。
C语言中,定义枚举类型变量,需要两步:
// 先定义类型
enum WEEK{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
// 再定义变量
enum WEEK day;
// 或者,同时定义类型和变量
enum WEEK {
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
// 或者,省略类型名称,直接定义变量(只能用一次)
enum {
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
使用enumeration:
enum WEEK day;
day = TUE;
printf( "%d", day)
定义的时候好理解,使用的时候这里非常容易混淆。
switch判断enumeration:
// 定义枚举
enum color { red=1, green, blue };
enum color favorite;
fav = blue
// 开始判断
switch (fav) {
case red:
printf("你喜欢的颜色是红色");
break;
case green:
printf("你喜欢的颜色是绿色");
break;
case blue:
printf("你喜欢的颜色是蓝色");
break;
default:
printf("你没有选择你喜欢的颜色");
}
循环遍历enumeration类型(前提:枚举中定义的数是连续的):
// 定义枚举
enum DAY {
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
// 循环遍历枚举
for (day = MON; day <= SUN; day++) {
printf("枚举元素:%d \n", day);
}
C语言中的结构体
实际上是类
的原型,是可以让用户自定义的数据结构。
定义一个结构的语法,例如:
struct Books {
char title[50];
char author[50];
char subject[100];
int id;
};
使用结构体:
struct Books b = {"C 语言", "RUNOOB", "编程语言", 123456};
printf("%s, %s, %s, %d", b.title, b.author, b.subject, b.id);
作为函数参数的结构体:
void func(struct Books b) {
b.title = "C语言";
b.author = "RUNOOB";
b.subject = "编程语言";
b.id = 123456;
}
和结构体相似,是让用户自定义的数据类型,只是允许在相同的内存位置
存储不同的数据类型。
定义语法:
union Data {
int i;
float f;
char str[20];
};
使用共用体:
union Data d;
d.i = 10;
d.f = 220.5;
strcpy( d.str, "C Programming" );
作为系统底层语言,经常需要操作内存等,如动态内存分配
,这是没有指针就无法完成的。
&
和*
每个变量都有两部分:⓵数据值 ⓶内存地址
而指针相当与一个反变量
,它也是两部分但是相反: ⓵内存地址 ⓶数据值
假设有一个变量int var = 100
,和一个指针int *ptr = &var
。
当我们把指针指向了那个变量的时候,实际上的操作只是:把⓵和⓶换个位置。
直接调用var
或ptr
的时候,都是显示⓵部分的值。
但是如果要显示隐藏在背后的⓶部分的值,就需要用到这两个操作符号:
&
作用于变量,用于查看变量的⓶部分的值:即内存地址,如&var
显示的是0x7fff5ccaf7dc。*
作用于指针,用于查看指针的⓶部分的值:即数据值,如*ptr
显示的是100。明白这个“互反”规则后,以后的**ptr
指向指针的指针也就好理解了。
假设现在有一个普通变量int height = 10;
,那么查看这个变量的内存地址
的方法就是打印&height
。
C指针也是一个变量
,但是它的值是另一个变量的内存地址。
声明C指针型变量的方法:
type *var-name;
// 如
int *ip; /* 一个整型的指针 */
double *dp; /* 一个 double 型的指针 */
float *fp; /* 一个浮点型的指针 */
char *ch; /* 一个字符型的指针 */
怎么给指针赋值?当然不能手动写,而是需要把另一个变量的内存地址传给它:
int variable = 1234;
int *addr;
addr = &variable;
这样就完成了指针的赋值。
怎么打印指针?
打印的时候要用特殊的%p
格式化符号,代表pointer
:
// 打印指针中的指向的内存地址:
printf("%p", addr);
// 等同于:
printf("%p", &variable);
// 获得这块内存中的存储值:即刚才赋值的1234
printf("%d", *addr);
参考:Making Peace with Double Pointers
一个指针ptr指向variable的内存地址,那么另一个指针pptr指向前一个指针ptr,就相当于得到了variable的值。有点像互反
的关系。
声明一个指针的指针:
int var;
int *ptr;
int **pptr;
var = 3000;
ptr = &var; // 获取变量的内存
pptr = &ptr; // 获取指针的值
// 打印
printf("%d\n", var );
printf("%d\n", *ptr );
printf("%d\n", **pptr);
// 输出结果全部都是3000
当然,你还可以设计triple pointer
,比如:int ***ppptr
,把它指向一个double pointer
: **ptr
.
// 声明函数
int max(int i, int j) { /* ... */ }
// 声明函数指针
int (* p)(int, int) = & max; // &可以省略
// 使用函数指针
p(12, 34)
// 声明普通函数
int max(int i, int j) { /* ... */ }
// 声明参数为指针的函数
void func( int *arr, int (*max)(void) ) { /* .... */ }
// 使用函数(正常用,只不过不会把参数“复制“进去,而是直接使用,相当于公用变量)
func( [1,2,3], max );
// 声明函数
void * func() { /* ... /* }
// 传递函数
int *ptr = func();
// 使用函数
ptr();
指针的算术运算有:
ptr++
,即ptr = ptr + 1
ptr--
,即ptr = ptr - 1
ptr = ptr + n
ptr = ptr - n
指针的p++
,p+1
, p--
, p-1
等,都是指的指针位移
。
假设一个指针指向了数组,如:
int arr[] = {123, 456, 789};
int *ptr = arr;
那么,实际上指针是指向了数组的第一个元素
。
另外,由于数组本质上也是个指针,所以不需要&arr
来得到内存地址。
也就说,这个时候,实际上ptr == &arr == &arr[0]
,这三项的值是一样的。
那么当ptr++
或ptr=ptr+1
的时候,指针移向了下一个位置,即arr[1]
的内存地址。这时:ptr == &arr[1]
。
同理,ptr--
只是往前位移了1而已。
既然指针ptr是一个简单的位置信息,那么我们同样可以比较指针
:
if (ptr < &arr[2])
printf("%d", ptr);
内存管理分static memory
静态内存和dynamic memory
动态内存。
一般当我们直接设置变量如int v = 99;
时,就是静态内存设定。这个变量的内存保留直到程序结束。
但当我们不知道变量是什么,它会动态变化时,我们就要动态设定内存了。
只有动态内存设定时,才会用到ma-la ca-la re-a-lac and free
。
主要是用<stdlib.h>
这个头文件,其中常用的方法有:
void *malloc(int num);
void *calloc(int num, int size);
void *realloc(void *address, int newsize);
void free(void *address);
光看名称有点迷惑,实际上:
malloc
-> m-alloc
-> Memory Allocation
realloc
-> Re-Allocation
calloc
-> c-alloc
-> Contiguous Allocation
记忆:像念绕口令一样重复念:
ma-la
ca-la
re-a-lac
andfree
. 参考:Allocating memory with malloc, calloc, realloc, and free
设定内存时,先要给指针设定,然后再用指针来储存具体的值。
最简单的内存设定方法就是malloc
:
// 单变量
int *v = malloc( sizeof(int) );
*v = 99;
// 长度为9的数组
int *arr = malloc( sizeof(int)*9 );
*arr[0] = 34;
*arr[101] = 99; // 这样直接写不好,很有可能超出界限
calloc
和malloc
区别只在于,calloc
有两个参数,一个是单项所占内存大小,另一个是多少个数量。
用法如下:
int *arr;
*arr = calloc( sizeof(int), 9 );
realloc
用于resize,即更改内存占用大小。第一个参数是指针,第二个参数是新的内存占用大小。
*new_arr = realloc( arr, sizeof(int)*9 )
free
用来清除内存。用完变量后即使清除是良好的实践。
如果不自己清除的话,很有可能造成对内存的占用累积越来越多,导致内存过载。
free( arr );
arr = NULL;
Valgind能够检测并动态显示某个二进制文件的内存占用信息,非常友好。
Mac安装:
$ brew install valgrind
Ubuntu安装:
$ sudo apt-get install valgrind
注意:Valgrind程序比较大,Ubuntu上需要150MB左右,Mac上50MB左右。
使用:
$ valgrind ./hello
注意:这里必须要用./
指定当前目录,而不能直接valgrind hello
。
开始后,会显示类似这样的信息:
在HEAP SUMMARY
中,
在LEAK SUMMARY
中,会显示内存泄漏的问题。
如果没有泄漏,则会在definitely lost
和indirectly lost
处显示0。
如果没问题的话,就会在最后的ERROR SUMMARY
那行显示0 errors from 0 contexts
参考:Learn GDB in 60 seconds 参考:使用 GDB 调试 Linux 软件 - IBM
GDB
是GNU出品的超强力debug工具,有人因为觉得是在命令行里手动调试而看不起它,但是真的很难说你用的哪个IDE调试器比这个强。
Facts:
GDB
支持所有的*nix系统,基本上已经是和gcc
,make
之类的成为系统标配。GDB
支持的调试语言有C, C++, Fortran, Assembly汇编。GDB
是在编译后的二进制文件上进行debugging的,而不是在源码上排错。对C/C++程序的调试,需要在编译时
就加上-g
选项:
$ gcc -g hello.c -o hello
$ g++ -g hello.cpp -o hello
$ clang -g hello.c -o hello
然后运行gdb来调试-g选项编译后的二进制文件:
$ gdb ./hello
进入调试后,按照顺序,一般的操作有:
break main
,即在main()
函数中断点。或break 30
在第30行断点。watch 变量名
run
print 变量名
step
next
continue
list
gdb
及其替代品gdb
对Mac的支持不好,经常会报错:
Unable to find Mach task port for process-id 82541: (os/kern) failure (0x5).
(please check gdb is codesigned - see taskgated(8))
而且问题很难处理,所以一般情况下是找个替代品。
cgdb
本身只是gdb的包装,让gdb更好看更好用。
Mac安装很简单:brew install cgdb
Ubuntu安装也一样:sudo apt-get install cgdb
配置很好看,默认自动分屏,用颜色显示。
但是,既然只是gdb的包装,所以gdb在Mac上的please check gdb is codesigned - see taskgated(8)
还是不能解决。
由于GDB在Mac上的一系列问题,lldb
属于Xcode默认调试器,无需任何配置。
唯一的问题就是,调试中的各个命令稍有不同。但是不用担心,学起来非常快。
参考:The LLDB Debugger 参考:GDB and LLDB Command Examples
常用命令(GDB与LLDB对比):
(gdb) break xx
-> (lldb) b xx
(gdb) run
-> (lldb) run
(gdb) step
-> (lldb) step
(gdb) next
-> (lldb) next
(gdb) until xx
-> (lldb) thread until xx
与gdb相比,目前已知缺点:很多IDE、GUI、插件都是基于gdb的,所以LLDB就与他们都无缘了。
fopen
最简单读写简单读文件:
FILE *to_read = fopen("test.txt", "r")
if (to_read == NULL) {
printf("Cannot open file\n");
return -1
}
char c;
while ( (c=fgetc(to_read)) != EOF) {
printf("%c", c);
}
fclose( to_read );
简单写文件:
open
读写参考视频:Reading and Writing Files in C, two ways (fopen vs. open)
注意一:
fopen
是Library call
,是建立在open
上进行封装、改善、加强易用性的调用。open
是System call
,是系统底层的文件读写调用。注意二:open
比fopen
慢100倍。
为什么?为什么即使fopen
是调用open
读写文件的,还要比open
慢呢?
是因为fopen
调用了缓存,即Buffered I/O
。
既然open
比fopen
还要慢,还要麻烦,为什么我们还要用它呢?
因为,*nix系统中一切皆文件,不光有文本文件,还有很多设备文件、pipes文件。而不是所有文件都需要缓存。类似设备这种,需要都是立马就操作,而不希望等待操作缓存的时间。
所以除了文本文件外,广泛的文件处理还是要用open
的,因为open
是底层的,意味着你能有更多的细节控制。
简而言之,fopen
和open
相当于高级语言和低级语言。高级语言用起来方便,低级语言可控性强。
副标题:Mac上的gdb无法正常调试的问题
Mac上用brew install gdb
安装gdb后,无法正常的运行run
命令,报错如下:
(gdb) break main
Breakpoint 1 at 0x100000f66: file a.c, line 4.
(gdb) run
Starting program: /Users/solomonxie/Workspace/tests/clang/a
Unable to find Mach task port for process-id 63414: (os/kern) failure (0x5).
(please check gdb is codesigned - see taskgated(8))
这个不是c程序的问题,也不是gdb的问题,而是Mac的问题。
参考:gdb doesn't work on macos High Sierra 10.13.3
为什么Mac不能调试?
"因为 Darwin 内核在你没有特殊权限的情况下,不允许调试其它进程。调试某个进程,意味着你对这个进程有完全的控制权限,所以为了防止被恶意利用,它是默认禁止的。允许 gdb 控制其它进程最好的方法就是用系统信任的证书对它进行签名。"
参考:gdb fails with “Unable to find Mach task port for process-id” error 参考:How to install and codesign GDB on OS X El Capitan
具体步骤如下:
用Spotlight搜索Directory Utility
程序,打开后,点击左下角解锁,然后打开菜单->Edit->Enable root user
->创建密码。
/System/Library/LaunchDaemon/com.apple.atrun.plist
文件将第22行的-s
改为-sp
然后保存退出。
一般来讲管理员是没有权限修改的,所以需要重启进入“安全模式”用root权限解开系统文件的保护,再重启,修改文件,再重启进入安全模式,再开启系统文件保护,再重启回到正常系统。
步骤为:
重启,黑屏时按住Ctrl-r
不松手一直到苹果标志出现。进入安全模式后,打开菜单Utilities-Terminal终端,输入csrutils disable
解锁系统文件保护。然后重启,回到正常系统中,sudo vim /System/Library/LaunchDaemon/com.apple.atrun.plist
将文件中22行-s
改为-sp
,保存退出。重启再次进入安全模式,命令行输入csrutils enable
锁定系统文件保护。再重启,回到正常系统,进行下一步。
brew uninstall --force gdb
打开系统的Applications -> Utilities -> Keychain Access
删除所有gdb相关的证书。
brew install gdb
打开系统keychain管理器:Keychain Access, go to menu Keychain Access-> Certificate Assistant -> Create a Certificate
。
创建新的证书,所填内容如下:
Name : gdb-cert
Identity Type: Self Signed Root
Certificate Type : Code Signing
[X] Let me override defaults
Serial Number : 1
Validity Period (days): 3650
Key Size : 2048
Algorithm : RSA
[X] Include Key Usage Extension
[X] This extension is critical
Capabilities:
[X] Signature
[X] Include Extended Key Usage Extension
[X] This extension is critical
Capabilities:
[X] Code Signing
[X] Include Subject Alternate Name Extension
Keychain: System
在Keychain管理器里,双击刚刚创建好的证书,在Trust
中全部选择为Always Trust
:
再打开命令行输入:
sudo killall taskgated
codesign -fs "gdb-cert" `which gdb`
launchctl load /System/Library/LaunchDaemons/com.apple.taskgated.plist
进入gdb调试程序,然后输入命令:
(gdb) set startup-with-shell off
然后正式开始调试。
如果调试没有问题,则将set startup-with-shell off
这句话写入~/.gdbinit
文件中,长久生效。
因为看到有人是由于更新了gdb或更新了os系统后才遇到问题,所以想是不是gdb版本与当前os版本不合的问题。 所以决定自己编译别的版本gdb。
官方各个版本的下载地址:https://ftp.gnu.org/gnu/gdb/ (经过测试,我的在MacOS 10.12 Sierra上编译各个新老版本gdb都编译不成功)
开始下载编译:
cd /tmp
wget https://ftp.gnu.org/gnu/gdb/gdb-7.12.1.tar.gz
cd gdb-*/
./configure --prefix=/opt/gdb-7.12 && echo [ OK ]
make && echo [ OK ]
sudo make install && echo [ OK ]
我当前的系统是MacOS 10.12 Sierra。相关的说法是:
"None GDB 7.11 or 7.12.1 will not work on Sierra 10.12.4 In short it's because of Apple security upgrade. We need to wait for re-enabling when some new version will shows up."
顺着这条思路搜索,找到一个有人已经编译好的gdb
二进制单文件。然后再用codesign
给它签名,竟然就可以用了!
在这里下载gdb7.12.1 sierra .zip 或在百度网盘下载。
解压后,备份并替换本机的gdb,放到/usr/local/bin/
中。然后pkill taskgated
并codesign -s gdb-cert /usr/local/bin/gdb
进行签名。
但是直接gdb
还不行,需要用sudo gdb ..
才能正常用。
注意:重新安装gdb后。第三方软件如cgdb
,需要重新安装才能使用,否则完全无法用。
Mac上LLDB
才是王道。Xcode默认调试器是LLDB,说明了苹果不鸟GNU。也有人说,GDB是过去,LLDB是将来。虽然不一定正确,但也证明了LLDB也很强大。
再有一点最重要的理由:你的项目生产环境真的是在Mac上吗? 既然生产环境不在Mac,为什么要用Mac编译? 这个逻辑一想通,就全通了—— 一般生产环境是在Linux服务器上的,所以你大可以共享项目文件夹给服务器,然后SSH进服务器进行编译调试。 如果只是学习语言用的小文件,那么更没必要用到强大的GDB功能,在Mac本地用LLDB即可。
所以,唯一的缺点就是用不了各类GDB的衍生品、GUI一类,排除这点,还是安心用LLDB吧,不要在Mac上折腾GDB了。。。
参考Youtube:A Tour of C's Many Operators
+
, -
, *
, /
, %
y = x++
或y = x--
,这里面x是自己+1或-1变新值
了,但是y获得的是x的旧值
。y = x++
或y = x--
,这里面x自己+1或-1变为新值
,y也同时获得x的新值
。==
, <=
, >=
, !=
<
, >
&&
||
!
.
,如Jason.age
->
,如Jason->age
,等同于(*Jason).age
参考:What does "->" mean in C/C++ programming?
二进制运算符
,就是先把数字转为二进制,然后对每一位进行运算。
&
, 比如9 & 5 = 1001 & 0101 = 0001 = 1
|
,比如9 | 5 = 1001 | 0101 = 1101 = 13
~
, 比如~9 = ~1001 = 0110 = 6
^
, "exclusive or", 只有0^1
和1^0
结果为1,而1^1
和0^0
结果都是0
XOR Gate
: 电路逻辑中的绝妙设计,即如果有两条线路同时运行:即1^1
或同时关闭:即0^0
,那么线路就全部关闭。这样轻松的就解决了线路冲突问题。
首先要确认位移的几个点:
左右位移:
<<
,比如9<<3
,意为9的二进制向左移动3位。>>
,比如9>>3
,意为9的二进制向右移动3位。左位移9<<2
的详细步骤:
9 十进制数
01001 转为二进制
~~~~~ 先确认总位数为5位
010_01000 因为向左移动了3位,后面空出来的用0补足
01000 因为下划线左边的部分超过了长度,所以要被删除
72 转换回十进制
右位移9>>2
的详细步骤:
9 十进制数
01001 转为二进制
~~~~~ 先确认总位数为5位
00001_001 因为向右移动了3位,前面空出来的用0补足
00001 因为下划线右边的部分超过了长度,所以要被删除
1 转换回十进制
快捷算法(数学意义):
十进制数 * 2^位移数
。如9<<3 = 9 * 2^3 = 72
十进制数 / 2^位移数
后只取整数部分。如9>>3 = 9 / 2^3 = 1.125 => 1
( (x>y) && ((a>b) || (a>c)) )
加法:
减法:
乘法:
除法:
总之就是一个
Σ Value * 2^index
的过程。
假设有一个二进制ABCDE
, 那么转换过程就是:
A * 2^4 +
B * 2^3 +
C * 2^2 +
D * 2^1 +
E * 2^0
比如二进制101011
,那么最方便的就是我们竖着写(建议从下往上写):
1 * 2^5 +
0 * 2^4 +
1 * 2^3 +
0 * 2^2 +
1 * 2^1 +
1 * 2^0
以上简化一下就是:
2^5 +
0 +
2^3 +
0 +
2 +
1
= 43
总之就是:不断除以2来取余数的过程,所有的余数按倒序排列出来就是二进制的结果。
假设十进制数43
,转换为二进制的过程就是:
43 / 2 = 21 ---mod---> 1
21 / 2 = 10 ---mod---> 1
10 / 2 = 5 ---mod---> 0
5 / 2 = 2 ---mod---> 1
2 / 2 = 1 ---mod---> 0
1 / 2 = 0 ---mod---> 1
那么结果按照倒序取余数,结果是:101011
C除了动态内存外,就语言本身而言,基本语法是非常好理解的,像Javascript一样简单。
只是,想用好C,就必须像Python一样知道各种内置函数、内置库。这样我们就不用重复造轮子了。
在C里,这个叫C Standard Library
,即C标准库。
那么怎么引用标准库呢?——通过导入xx.h
头文件。在讲标准库之前,先来讲讲*.h
头文件。
本质上来说,*.c
和*.h
是一模一样的:你可以在xx.h
中写具体的代码实现,然后编译成可执行文件;也可以在别的文件中导入xx.c
。
实际上,你用xx.hhh
,xx.abc
,是没区别的,一样可以在include中引用。只是习惯做法而已。
基于历史原因,各种编译器都已经明确了.c
和.h
的优化编译方法。如果混淆来写的话,对编译器是不友好的,对别人的阅读也是不友好的。
"xx.h"
与<xx.h>
的区分#include "myHeader.h"
#include <stdHeader.h>
当我们在include导入外部文件时候,编译器需要寻址
。"xx.h"
用来指定库的具体路径(包括系统路径下的标准库和其它路径的第三方库),而<xx.h>
只单纯指定系统路径下的库,也就是标准库。
也就是说,"xx.h"
这种写法,多了一个功能:可以指定具体路径。
为了更友好的展示代码,我们还是保留<xx.h>
是标准库,"xx.h"
是第三方库的好习惯。
声明
vs. 定义
简单说:
❖ C Intro
C语言的代码运行速度相当于
汇编语言
的运行速度,所以一般用C语言作为系统级开发语言。环境搭建
C语言只需要一个编译器
C Compiler
就可以工作,代码直接用各种文本编辑器写就行了。 目前比较流行的C编译器有:gcc
: 是GNU出的编译器,即GNU C Compiler
用
gcc -v
检查当前编译器版本,如果没有,则需要安装gcc了。Ubuntu上,
Mac上,需要在App Store安装
Xcode
,然后就有了gcc。Windows上,需要安装
MinGW
编译和运行C代码
假设现在写了一个最最最简单的
hello-world.c
,如下:最最简单的编译命令就是:
然后目录中就会自动生成一个
a.out
的文件,是个二进制机器可读懂的东西。这个名字a.out
是默认的,因为我们没有自己指定名称。如果要指定输出名称(-o 参数):
这时候目录就会出现一个叫
hello
的没有后缀名的文件。运行: 因为C代码编译出来是
二进制可执行文件
,也就是文件默认就有x
属性,所以我们可以像bash脚本一样直接在命令行打开:这样就会显示出
Hello, world!%
这样的信息。C的数据类型
C的数据类型分为几大类,各个类下有非常多的细分类型。 与动态类型的Python等语言不同,作为静态类型的C语言必须严格区分类型,这样才能在创建变量时候就给它开辟一块固定的内存空间。
C的数据类型分为这几大类:
其中,
整数类型
可以细分为以下:所以为了确定当前OS和架构环境下的类型所占内存,需要用
sizeof(type)
方法来检验。变量类型
C变量分为如下类型:
C语言声明变量:
定义常量,有两种方法:
存储类
C中的
存储类
可以定义变量或函数的生命周期
和活动范围
,放在type
前作为修饰。 一般分为:寄存器
变量,即变量值直接存在CPU寄存器中,极快。但是空间限制很大。extern
:即external,如果被extern修饰,则代表引用外部变量,而不重新分配空间和赋值条件判断
if...else
单句if...else:
多句:
switch
三元运算符
循环
循环分为:
循环控制语句有:
break
:退出循环。continue
:进入下一次循环。goto
:不推荐。这是汇编式的指针移动,会导致逻辑混乱。for循环
无限for循环:
while循环
无限while循环:
do...while循环
使用函数
不同于高级语言,C语言的函数分为
声明
和定义
,函数头相当于要写“两遍”。 函数的定义和声明都可以在任意位置写, 但是必须在正式使用前写其中一个,否则编译器会找不到引用。示例:
以上为最简单的函数调用,且传输的参数值
3 & 90
,会被复制
到函数中使用。函数中无论怎么改,都不影响原先的变量值。但是,如果想要允许函数修改原变量的值,那么就不能直接传递值,而是要
传递引用
。即把每个变量的内存地址的指针传进去:注意,其中函数参数声明时候是在变量前加
*
,而使用时,传入变量引用需要在变量前加&
。局部变量和全局变量
和其它语言一样。
但是要注意: 当局部变量被定义时,系统不会对其初始化,必须手动去初始化。 定义全局变量时,系统会自动对其初始化。
形参与实参
C的函数的参数有很多种类型:
函数返回数组
C语言不支持直接返回数组,但是可以返回一个指针,指向数组。