1.函数调用
- 传值调用:在函数内部修改形式参数,不改编实际参数的值;
- 引用调用:即指针调用,传入的是变量的指针,则在函数内部修改形式参数,实际参数跟着改变。
2. 数组
- 数组名即该数组的首地址;
- 首地址:一段内存空间中第一个存储单元的地址;
- 一维数组作为函数参数三种方法:函数定义方法void my_function(int *a)、void my_function(int a[10])、void my_function(int a[]),就函数定义而言,数组的长度是无关紧要的,因为C不会对形式参数执行边界检查,这三种方法传递数组的本质都是传递一个指针,即数组的首地址;
- 从函数返回数组:如果想从函数返回一个一维数组,必须声明一个返回指针的函数int *my_function(){},另外C不支持在函数外部返回局部变量的地址,除非定义局部变量为static,即静态变量。
- 指针加减:以指针所指向的类型空间为单位进行偏移。
- 二维数组:int a[3][4]={{6,3,2,4},{2,1,3,1},{2,3,1,4}};a代表的是数组中第一个存储单元即{6,3,2,4}的地址,同理a+1是第二个存储单元即{2,1,3,1}的地址。如访问a[1][2],还可通过*(a[1]+2)、*(*(a+1)+2)访问。
- 二维数组作为函数参数三种方法:int a[3][4],int a[][4],(int *p)a[4],数组的一维长度可以不写,但二维必须要写,因为实参传递来的是数组全局第一个元素6的起始地址,如果形参中不说明列数,编译器将无法定义其它元素的位置。
3. 函数指针与回调函数
一个数据变量的内存地址可以存储在相应的指针变量中,函数的首地址也以存储在某个函数指针变量中。这样,我就可以通过这个函数指针变量来调用所指向的函数了。
在C系列语言中,任何一个变量,总是要先声明,之后才能使用的。函数指针变量也应该要先声明。函数指针变量的声明:
void (*funP)(int) ; //声明一个指向同样参数、返回值的函数指针变量。
- 函数指针:一个指向函数的指针变量,函数指针可以像一般函数一样,可以调用函数、传递参数;
- 函数指针变量的声明:int (*fun_ptr)(int, int);
- 回调函数:函数指针变量可以作为某个函数的参数来使用的,回调函数是一个通过函数指针调用的函数。
4. 打印输出
- %x输出地址(int数组指针加1,则地址加4,因为一个整数占4个字节)
- %d 输出整数
- %f 输出小数(单精度双精度),
- %.2f 输出到小数点后两位
- %c 输出字符
- %s 输出字符串: 声明字符串str,字符串本质是字符数组,末尾自动以‘\0’作为字符串结束的标志,‘\0’不计入字符串长度:Char str = {‘H’,’e’,’l’,’l’,’o’};printf(“%s”,str)(给出字符串的首地址即可打印该字符串)
5. 结构体
数组存储的是相同类型的变量,结构体允许存储不同类型的数据项。
- 结构体定义:struct tag
{member_list;
- member_list;
- …
- }variable_list;
- 结构体定义必须使用struct语句,tag是结构体标签,member_list是变量定义,variable_list是结构体变量。
- …
- 访问结构体成员:结构体变量通过.访问,结构体指针通过->访问;
- 结构体变量可作为函数参数;
- 结构体指针:struct tag *p,是一个该结构体类型的指针,不能直接对该指针里的内容赋值,错误操作:p->a = 10;需先将一个同类型结构体变量地址赋值给它。就像int *p,错误操作:*p = 10;凡是指针必须先指定指针里面存放的地址才能对指针里面的内容进行赋值更改;
- 指向自己类型的指针:结构体里除了一些其它数据类型还有一个指向自己类型的指针,通过该指针可将各结构体连接起来形成一个链表,可用于快速删除和添加节点。
struct NODE { int a; struct NODE *next_node; };
- struct NODE node1={12},node2={13},node3={14};
- node1.next_node = &node3;(此时node1里面的指针存放node3地址)
- node3.next_node = &node2;(此时node3里面的指针存放node2地址) printf(“%d”,node1.next_node->next_node->a);输出13。
- node1.next_node = &node3;(此时node1里面的指针存放node3地址)
-
6. 位域
位域:有些信息在存储时,并不需要占用一个完整的字节,例如在存放一个开关量时,只有 0 和 1 两种状态,用 1 位二进位即可。为了节省存储空间,并使处理简便,C 语言又提供了一种数据结构,称为"位域"。
- 一个位域存储在同一个字节中,如一个字节所剩空间不够存放另一位域时,则会从下一单元起存放该位域
- 由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位
- struct bs{ int a:8; int b:3; int c:5; }data; 其中 data 为 bs 变量,共占两个字节。其中位域a占8位,位域b占3位,位域c占5位。bit.b=7; /* 给位域赋值应注意赋值不能超过该位域的允许范围,b占3位,所以赋值不能超过8 */
7. 共用体
必须使用union语句。允许在相同的内存位置存储不同的数据类型。你可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体占用的内存应是共用体中最大成员所占的字节数。
- union Data { int i; float f; char str[20]; } data;
8. 字符串
- 单字符输入输出:要想输入输出多个字符,可使用循环。int c = getchar()从键盘输入一个字符,返回的是该字符的ASCII;putchar(c),接收的是ASCII吗,在屏幕上输出的是对应的字符;
- gets(str)输入字符串,一般按空格键可结束输入,puts(str)输出字符串。
9. fseek
- int fseek(FILE *stream , int offset , int whence);(如果成功则该函数返回零,否则返回非零)
whence:指定了指针从哪个位置开始偏移,一般指定为以下常量
SEEK_SET(文件的开头)
SEEK_CUR(文件指针的当前位置)
SEEK_END(文件的末尾)
offset:相对whence的偏移量,以字节为单位
stream:指向FILE对象的指针
10. fread
- size_t fread(*ptr , size_t size, size_t nmemb, FILE *stream);(成功会返回元素个数)
ptr -- 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针。
size -- 这是要读取的每个元素的大小,以字节为单位。
nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。
11. fwrite
- size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
ptr -- 这是指向要被写入的元素数组的指针。
size -- 这是要被写入的每个元素的大小,以字节为单位。
nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。
12. ftell
- int ftell(FILE *stream);(返回当前指针的位置)
stream--这是指向FILE对象的指针,该FILE对象标识了流。
13. calloc
- void *calloc(size_t nitems, size_t size)
nitems -- 要被分配的元素个数。
size -- 元素的大小。
malloc 和 calloc 之间的不同点是,malloc 不会设置内存为零,而 calloc 会设置分配的内存里面内容为零。
14. memset
- memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符(按字节填充,比如
int *a = NULL;
a = (int*)calloc(3,sizeof(int));
memset(a, 1, 2);//将前面两个字节填充1,所以第一个数应该是00000000 00000000 00000001 00000001即257,第二、三个数都为0,因为calloc分配内存时会设置内存为0。
对于有符号的整数:先转化为二进制,然后取反码,再取补码(反码+1)
比如十六进制0xffff e0ba
原码:1111 1111 1111 1111 1110 0000 1011 1010(二进制码)
反码:1000 0000 0000 0000 0001 1111 0100 0101(第一位是符号位)
补码:1000 0000 0000 0000 0001 1111 0100 0110(反码加1)
十进制:-8006
15. 引用
- 引用的作用:给变量起别名
注意事项:
int a = 10;
int b = 10;
int &c; //错误,引用必须初始化;
int &c = a; //一旦初始化就不可以修改
c = b; //这是赋值操作,不是更改引用;
引用的本质:引用的本质是一个指针常量(指针指向不可以修改)
int a = 10;
int &ref = a; //int* const ref = &a;(编译器会这样做)
ref = 20; //*ref = 20(编译器会这样做)
常量引用
使用场景(用来修饰形参,防止误操作): 加上const在函数体里就不能对v进行修改了,如果不加const,函数体里面对v修改了,则函数外相应也会被修改;
void fun_name(const int &v) {
函数体;
}
int &ref = 10; //会提示错误,引用必须引用一块合法的内存空间
const int &ref = 10; //加上const之后就可以,加上const之后,编译器会将代码修改为:
int temp = 10; const int &ref = temp;
ref = 20; //会提示错误,加入const后变为只读,不可以做修改。
17. 内存分区模型
c++程序在执行时,将内存大方向划分为4个区域:
- 代码区:存放函数体的二进制代码,由操作系统进行管理;
- 代码区是共享的,共享的目的是对于多次双击执行的程序,只需在内存中有一份代码即可;
- 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令;
- 全局区:存放全局变量和静态变量及常量(字符串常量、const修饰的全局常量);
- 该区域的数据在程序结束后由操作系统释放;
- 栈区:由编译器自动分配释放,形参数据、局部变量等;
- 栈区的数据在函数执行完后自动释放,所以不要返回局部变量的地址;
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收;
- c++中主要用new在堆中开辟内存, 用delete释放堆中的内存;
- int *p = new int(3); //利用new创建的数据,会返回该数据类型的指针;
- cout<<*p<<endl;//输出3
- delete p;
- cout<<p<<endl;//会报错,该内存已释放,再去访问就属于非法访问;
- //在堆区创建数组
- int *arr = new int[3]; //代表数组有3个元素,返回数组的首地址;
- delete[] arr; //释放数组的时候,要加[]才可以;
17. 空指针和野指针
空指针:指针变量指向内存编号为0的空间;
用途:初始化指针变量;
注意:空指针指向的内存是不允许访问的;
野指针:指针变量指向非法的内存空间;
int *p = (int *)0x1100; //指针变量p指向内存地址编号为1100的空间;
cout<<*p<<endl; //访问野指针报错(因为这段内存并没有申请,只是直接指向它)
18. const修饰指针
常量指针:const修饰指针,指针的指向可以修改,但是指针指向的值不可以修改;
const int *p = &a;
*p = 20; //错误,指针指向的值不可以修改;
p = &b; //正确,指针指向可以修改
指针常量:const修饰常量,指针的指向不可以修改,指针指向的值可以修改;
int * const p = &a;
*p = 20;//正确,指向的值可以修改;
p = &b;//错误,指针指向不可以修改
const既修饰指针又修饰常量:指针的指向和指针指向的值不可以修改;
const int * const p = &a;
19. 函数默认参数
如果某个参数已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值;
如果函数声明有默认参数,函数实现就不能有默认参数(声明和实现只能有一个有默认参数);
20. 函数的分文件编写
- 创建.h头文件
- 创建.cpp源文件
- 在头文件中写函数的声明
- 在源文件中写函数的定义