整数运算
两个整数运算结果也只能是整数
a+=5等价于a=a+5
a=b+5等价于a=a(b+5)
a++等a+=1等a=a+1
a++是a加1以前的值,++a是a加1后的值
运算符优先级:算数>关系>赋值,判断是否相等的优先级比大于小于低
所表达的数的范围
char< short<int< float<double
sizeof()
sizeof()是一个运算符,给出某个类型或变量在内存中所占据的字节数
sizeof()是静态运算符,它的结果在编译时刻就决定了,不要在 sizeof的括号里做运算,这些运算不会做的
unsigned
unsigned:如果一个字面量常数想要表达自己是 unsigned,可以在后面加u或U,255U用l或L表示long(long)
unsigned的初衷并非扩展数能表达的范围,而是为了做纯二进制运算,主要是为了移位
整数的输入输出
只有两种形式:int或 long long
%d: int
%u: unsigned
%ld: longlong
%lu: unsigned long long
选搔整数类型
为什么整数要有那么多种?为了准确表达内存,做底层程序的需要
没有特殊需要,就选int
现在的CPU的字长普遍是32位或64位,一次内存该写就是个int,一次计算也是一个int,选择更短的类型不会更快,甚至可能更慢
现代的编译器一般会设计内存对齐,所以更短的类型实际在内存中有可能也占据一个int的大小(虽然sizeof告诉你更小)
unsigned与否只是输出的不同,内部计算是一样的
printf输出inf表示超过范围的浮点数:±∞
print输出nan表示不存在的浮点数
带小数点的字面量是 double而非 float
float需要用或F后綴来表明身份
1.234f
判断两个浮点数是否相等可能失败
fabs(fl-2)< le-2//两个浮点数相减的绝对值小于一个很小的数,可认为两个浮点数相等
选浮点类型
如果没有特殊需要,只使用 double
现代CPU能直接对 double做硬件运算,性能不会比float差,在64位的机器上,数据存儲的速度也不比float慢
自动类型转换
当运算符的两边出现不一致的类型时,会自动转换成較大的类型
大的意思是能表达的数的范围更大
char——> short——>int——>long——>long long
int——> float——> double
对于 printf,任何小于int的类型会被转换成int,float会被转换成 double
但是scanf不会,要输入short,需要%hd
强制类型转换
要把一个量强制转换成另一个类型(通常是較小的类型),需要:(类型)值
比如:
(int)10.2
(short)32
注意这时候的安全性,小的变量不总能表达大的量
(short)32768
只是从那个变量计算出了一个新的类型的值,它并不改变那个变量,无论是值还是类型都不改变
强制类型转换的优先级高于四则运算
逻辑运算符优先级:
!>&&>||
短路
逻辑运算是自左向右进行的,如果左边的结果已经能够决定结果了,就不会做右边的计算
a==6&&b==1
a==6&&b+=1
对于&&,左边是 false时就不做右边了
对于‖,左边是true时就不做右边了
tip1
不要把赋值,包括复合赋值组合进表达式!
一个代码内有重复相似段是程序质量不良的表现
函数是一块代,接收零个或多个参数做一件事情,并返回零个或一个值
数组的大小
sizeof给出整个数组所占据的内容的大小,单位是字节
sizeof(a)/sizeof(a[0])
sizeof(a[0])给出数组中单个元素的大小,于是相除就得到了数组的单元个数
这样的代码,一旦修改数组中初始的数据,不需要修改遍历的代吗
数组的操作
遍历数组:通常都是使用for循环,让循环变量i从0到<数组的长度,这样循环体内最大的正好是数组最大的有效下标
数组作为函数的参数时:不能在[]中给出数组的大小,不能再利用 sizeof来计算数组的元素个数!
数组作为函数参数时,往往必再用另一个参数来传入数组的大小
作为参数的指针
void f(int p)
在被调用的时候得到了某个变量的地址
int i=O; f(&i)
在函数里面可以通过这个指针访问外面的这个
函数参数表中的数组实际上是指针
sizeof(a)==sizeof(int )
但是可以用数组的运算符进行运算
指针是const
表示一旦得到了某个变量的地址,不能再指向其他变量
int const q=&i;//q是 const
q=26;//OK
q++; //ERROR
所指是 const
表示不能通过这个指针去修改那个变量(并不能使得那个变量成为 const)
const int p=&i
p= 26; //ERROR!
i=26;//OK
P=&i;//OK
const
lnt i
const int p1 =&i
int const p2=&i
int const p3=&i
判断哪个被const了的标志是const在的前面还是后面
const p指针不许动
const p指针不许动变量
转换
总是可以把一个非 const的值转換成 const的
void f(const int* x)
int a =15:
f(&a);//ok
const int b = a
f(&b);//ok
b =a+1: // Error
当要传递的参数的类型比地址大的时候,这是常用的手段:既能用比較少的字节数传递值给参数,又能避免函数对外面的变量的修改
const数组
const int a[]={1,2,3,4,5,6};
数组变量已经是 const的指针了,这里的 const表明数组的每个单元都是 const Int所以必须通过初始化进行赋值
保护数组值
因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值
为了保护数组不被函数破坏,可以设置参数为 const
int sum(const int a[], int length);
指针
int p :指针加一p+1是指加上一个sizeof(int),将指针移到下一个单元
int p=a[];
p —>a[0]
(p+1)—>a[1]
*(p++)可以遍历数组
给一个指针加|表示要让指针指向下一个变量
int a[l0]
int p= a
(p+1)—>a[1]
如果指针不是指向一片连分配的空间,如数组,则这种运算没有意义
(p+n)<—>a[n]
p*q两个指针相减p-q,等于q加多少个单元等于p
p++
取出p所指的那个数据来,完事之后顺便把移到下一个位置去
的优先级然高,但是没有++高
常用于数组类的连续空间操作
在某些CPU上,这可以直接被翻译成一条汇编指令
指针乘除无意义
指针遍历数组方法
1 | *p a[10] |
指针比較
<,<=,==,>,>=,!=都可以对指针做
比较它们在内存中的地址
数组中的单元的地址肯定是线性从小到大递增的
0地址
当然你的内存中有0地址,但是0地址通常是个不能随便碰的地址
所以你的指针不应该具有0值
因此可以用0地址来表示特殊的事情:
1返回的指针是无效的
2指针没有被真正初始化(先初始化为0)
NULL(必须是大写)是一个预定定义的符号,表示0地址
有的编译器不愿意你用0来表示0地址
指针的类型
无论指向什么类型,所有的指针的大小都是一样的,因为都是地址
但是指向不同类型的指针是不能直接互相赋值的
这是为了避免用错指针
指针的类型转换
void表示不知道指向什么东西的指针
计算时与char相同(但不相通)
指针也可以转换类型
intp =&i
voidp<—>(void *)p
这并没有改变p所指的变量的类型,而是让后人用不同的眼光通过p看它所指的变量
我不再当你是int,我认为你就是个void!
用指针来做什么
需要传入較大的数据时用作参数
传入数组后对数组做操作
函数返回不止一个结果是
需要用函数来修改不止一个变量
动态申请的内存
内存操作
malloc(跟系统要一块内存)
1 | #include <stdlib. h> |
如果申请失敗则返回0,或者叫做NULL
free()
把申请得来的空间还给“系统”
申请过的空间,最终都应该要还
只能还申请来的空间的首地址
free常见问题
申请了没free—>长时间运行内存逐漸下降
新手:忘了
老手:找不到合的free的时机
free过了再free
地址变过了,直接去free
字符串
char a[]={‘h’,’a’,’l’,’l’,’o’,’!’}//字符数组
char a[]={‘h’,’a’,’l’,’l’,’o’,’!’,’\0’}//字符串
以0(整数0)结尾的一串字符
0或’\0’是一样的,但是和0不同
0标志字符串的结束,但它不是字符串的一部分
计算字符串长度的时候不包含这个0
字符串以数组的形式存在,以数组或指针的形式访问
更多的是以指针的形式
string.h里有很多处理字符串的函数
1 | char*str="Hello" |
字符串常量
Char s=”Hello, world”
●s是一个指针,初始化为指向一个字符串常量
●由于这个常量所在的地方,所以实际上s是 const
chars,但是由于历史的原因,编译器接受不带const的写法
●但是试图对s所指的字符串做写入会导致严重的后果
char S[]=”Hello, world”//这个字符串就在我这里
Char* s=”Hello, world”//指向某个地方的字符串
数组:这个字符串在这里(作为本地变量,空间自动被回收)
指针:这个字符串不知道在哪里(处理参数,动态分配空间)
如果要构造一个字符串一>数组
如果要处理一个字符串一>指针
字符串输入输出
1 | char string[8]; |
空字符串
1 | char buffer[100]=""; |
复制一个字符串
1 | char*dst=(char*)malloc(strlen(src)+1) |
枚挙
枚挙是一种用戶定义的数据类型,它用关键字enum以如下语法来声明
enum枚挙类型名字{名字0,……,名字n};
枚挙类型名字通常并不真的使用,要用的是在大括号里的名字,因为它们就是就是常量符号,它们的类型是int,值则依次从0到n。如:
enum colors {red, yellow, green};
就创建了三个常量,red的值是0, yellow是1,而 green是2。
当需要一些可以排列起来的常量值时,定义枚举的意义就是给了这些常量值名字。
声明枚挙量的时候可以指定值
enum COLOR {RED=1, YELLOW, GREEN =5};
结构
声明结构的形式
1 | struct point{ |
和本地变量一样,在函数内部声明的结构类型只能在函数内部使用
所以通常在函数外部声明结构类型,这样就可以被多个函数所使用了
结构指针
和数组不同,结构变量的名字并不是结构变量的地址,必须使用&运算符1
struct date*pdate= &today;
结构作为函数参数
1 | int numberofdays(struct date d) |
整个结构可以作为参数的值传入函数
这时候是在函数内新建一个结构变量,并复制调用者的结构的值
也可以返回一个结构
这与数组完全不同
指向结构的指针
1 | struct date { |
全局变量初始化
没有做初始化的全局变量会得到0值
指针会得到NULL值
只能用编译时刻已知的值来初始化全局变量
它们的初始化发生在main函数之前
全局变量不应该和另一个全局变量有联系
同名变量,本地变量优先级高于全局变量,即本地变量隐藏了全局变量
静态本地变量(全局生存期,本地作用域)
在本地变量定义时加上 static修饰符就成为静态本地变量
当函数离开的时候,静态本地变量会銖存在并保持其值
静态本地变量的初始化只会在第一次进入这个函数时做(只做一次初始化),以后进入函数时会保持上次离开时的值
静态本地变量实际上是特殊的全局变量,它们位于相同的内存区域
静态本地变量具有全局的生存期,函数内的局部作用域
static在这里的意思是局部作用域(本地可访问)
不要使用全局变量来在函数间传递参数和结果
尽量避免使用全局变量
丰田汽车的案子
使用全局变量和静态本地变量的函数是线程不安全的
返回指针的函数
返回本地变量的地址是危险的
返回全局变量或静态本地变量的地址是安全的
返回在函数内 malloc的内存是安全的,但是容易造成问题
最好的做法是返回传入的指针
编译预处理指令
开头的是编译预处理指令
它们不是C语言的成分,但是C语言程序离不开它们
#define(纯文本替换)
define用来定义一个宏
define<名字><值>
注意没有结尾的分号,因为不是C的语句
名字必颁是一个单词,值可以是各种东西
在C语言的编译器开始编译之前,编译预处理程序
(cpp)会把程序中的名字换成值
完全的文本替换
acc-save-temps
宏
如果一个宏的值中有其他的宏的名字,也是会被替换的
如果一个宏的值超过一行,最后一行之前的行末需要加 \
宏的值后面出现的注释不会被当作宏的值的一部分
预定义的宏
1 | _LINE_ |
带参数的宏
1 | #define cube(x) ((x)*(x)*(x)) |
在大型程序的代吗中使用非常普遍
可以非常复杂,如“产生”函数:在#和##这两个运算符的帮助下
存在中西方文化差异
部分宏会被inline函数替代
变量的声明
int i;是变量的定义
extern int i;是变量的声明
声明和定义
声明是不产生代码的东西
函数原型
变量声明
结构声明
宏声明
枚挙声明
类型声明
inline函数
定义是产生代码的东西
头文件放声明是规则
重复声明
同一个编译单元里,同名的结构不能被重复声明
如果你的头文件里有结构的声明,很难这个头文件不会在一个编译单元里被#include多次,所以需要”标准头文件结构”
标准头文件结构(宏的if)
1 | #ifndef _LIST_HEAD_ |
链表
0建一个node结构
1首选有个node结构(作为节点),结构里有int(或者其他类型)变量去存数据,还有个同样结构的*next(next要等于下一个节点)(套娃)
2读入数据,并新建一个node结构p,要让一个node结构head始终等于链表第一个节点
3要有一个node结构last,每次要让last从head开始,直到last->next是空的,然后让next等于新的p结构(这样就链起来了)
4每次读入新的数据,就新建一个p,然后让last从head开始遍历链表,直到last->next是空的,然后然后让next等于新的p结构
要在函数内改变指针的指向,就要传指针的指针进去
遍历链表
1 | for(p=list.head; p; p=p->next){} |
但要让删除链表中某一结点,需要另一个指针q,q一开始为null,后来始终指向p前一个节点
当p找到了要删除的节点,就让q->next等于p->next,然后free(p)
for(q=null,p=list.head; p; q=p,p=p->next){}
当然还要判断链表的第一个元素是不是我们要删除的
如果是,就不能让q->next等于p->next,因为q一开始是NULL
我们应该让head->next等于p->next,然后free(p)
“.”一般情况下读作”的”。
“->”一般读作”指向的结构体的”。
清除整个链表
1 | for(p=head;p;p=q){ |
main()
main(成为C语言的入口函数其实和C语言本身无关,你的代码是被一小段叫做启动代的程序所调用的,它需要叫做main的地方)
操作系统把你的可执行程序装载到内存里,启动运行,然后调用你的main函数
在不同操作系统,入口函数可能不是main()