C/C++学习笔记汇总
^函数重载的匹配:
当函数名被重载后,函数的匹配过程:首先寻找能精确匹配的函数,如果未能精确匹配,则尝试找一个可以模糊匹配的函数。
1)精确匹配:参数个数相同,类型相同。
2)模糊匹配:参数个数相同,类型不同,但支持隐式转换。
^参数默认值
1)具有默认值的参数必须要放在后面。
2)当函数声明与定义分开时,应把默认写在声明里,不能写在定义里。
void show(int x,int y,int z=1); //默认值加在函数声明里
int main()
{
...
}
void show(int x,int y,int z) //函数定义之处不能加默认值
{
...
}
^内联函数:
在函数前加一个inline关键字,该函数称为内联函数。
^函数的递归调用:
1)将高阶问题转化为低阶相同问题。
2)必须设置终止条件,避免无限制递归。
3)可以替换为非递归算法,改善循环语法。
4)控制递归深度。
***指针^内存地址的表示: a在内存中对应4个字节;
unsigned int a=0xA0A0A0A0; //a内存中的值:A0 A0 A0 A0
a = 0xB1B1B1B1; //a内存中的值:B1 B1 B1 B1 变量地址是一个整数,可以用操作符&来取得。
例如: inta = 0;
double b = 0;
printf("%08X\n"&a); //把地址按十六进制来打印
printf("%08X\n"&b); /*把址按照十六进制来打印^指针的概念: XXX* 表示XXX型变量的地址。可以为char,int,double等,这种带*的类型叫做指针类型。*/
指针(Pointer)意思是Point to an address.例如:
int a = 1;
int *p = &a; //定义了一个int*型变量p,其值为a的地址。
1)指针变量也是变量。
2)不同类型的指针,不能互相赋值。
3)指针是一个整数类型。 在用printf打印时,通常使用的格式符为%p,p代表pointer。
4)*位置比较随意。
5)同类型指针可以混合定义。
^星号操作 “*”可以用来修改内存值,用在指针变量上可以直接读写内存的值。星号操作是一种按地址访问的技术,只要知道了这块内存的地址,就可以直接修改这块内存的值。
1)只有指针类型才支持星号操作。 int addrress = 0x12345678; *addr = 0 //编译错误!只有指针才支持星号操作
2)其他指针类型的用法是一样的。
3)区分星号的上下文^指针与数组 数组在内存中就相当于一串紧密排列的变量,数组名代表的就是这一块内存的首地址。
^指针加减法^指针与数组的转换
1)p指向arr的任意一个值; p = arr+3; //指向arr[3] p = &arr[3]; //指向arr[3]
2)给数组元素赋值; arr[3] = 10; 或: *(arr+3) = 10; 或: int* p=arr+3; *p = 10;
3)把p当成数组使用 int* p = &arr[1]; //p指向arr[1] p[0] = 0xAA; //p[0]:自p开始的第0号元素 p[1] = 0xBB; //p[1]:即arr[2]
4)长度为1的数组; 普通变量int a可以视为长度为1的数组来操作。 int a = 10; int* p = &a; p[1] = 11; //长度为1的数组5)
数组的遍历;
方法一: int arr[4] = {1,2,3,4}; for(int i = 0;i<4;i++) { printf("%d\n",arr[i]); }
方法二:用指针遍历,注意终止条件为P来访问对象的成员,例如: p->id = 20141011; //使用->访问对象成员 strcpy(p->phone,"15928682083"); //使用->访问对象成员 也可以使用(*p).id,但是不常用。
3)做为函数参数 和基本类型一样,结构体也可以作为函数参数
4)做为函数返回值
5)作为结构体成员^结构体的特殊写法 结构体定义允许放在函数内部,这么定义的类型只能在函数内可见。由于struct语法的初衷是要定义一个呗多处使用的自定义类型,正常情况下应该定义在函数体之外。
^结构体的命名 结构体命名:“数字、字母、下划线的组合,不能以字母开头”。其次,命名要有意义,一个好的名字应该直接反映其意义。下面有两种常见的格式:
1)纯小写,以下划线分开每个单词,例如good_job,large_buffer.
2)每个单词以大写开头,在C++中推荐使用这种风格,结构体内成员变量,通常是小写,并以下划线分割每个单词。
^传值与传地址 在传输参数时,如果传入的是一个对象的值,叫“传值”方式,如果传入的是一个对象的地址,叫做“传地址”方式。
1)传值方式
2)传地址方式:
***联合体
1)概念:
***动态内存分配
1)动态内存分配malloc申请内存 应用程序调用malloc函数可以申请一块指定大小的内存,函数原型为: #includevoid* malloc(int size); 参数:size:内存空间的大小,以字节为单位。 返回值:申请出来的这块内存首地址。 用法示例: char* p = (char*) malloc(84); //申请一块84字节的空间 内存管理空间并不关心这一块内存的用途,所以malloc的返回值是void* ,仅表示内存的地址。应用程序可以用来存储任何类型的数据。例如申请一块100个int型数据,示例如下 int* p = (int*)malloc(100*4); //申请100*4内存 for(int i = 0;i<100;i++) { p[i] = i * i; //使用这块内存 }
free释放内存
#includevoid free(void* ptr); 在使用完毕后,应用程序应当调用free函数来释放内存,当内存交给内存管理器,传入的参数就是先前用malloc得到的指针.
2)内存管理器与堆 内存管理器(MM)的职责就是提供内存服务,它管理的区域称为堆,malloc得到的内存的位置是在堆区。原则:尽可能少的申请内存,尽可能快的释放。
堆内部管理:MM对借出的内存块进行标识: (p0,n0)(p1,n1)(p2,n2)... 它内部已经保证任意两块不会交叠,不会把一块内存同时借给两个应用程序使用。 内存泄漏
3)对象的生命期 对象的分类:全局对象,局部对象,动态对象。 ①当定义一个变量时:Object a;,则变量a对应了一个对象,类型为Object,地址为&a,如果这个变量是全局变量,则a称为全局对象,如果这个变量是局部变量,则a称为局部对象。 当用malloc动态申请内存时:Object* p = (Object*)malloc(sizeof(Object));此时p指向了一个对象。该对象内存使动态分配的,称为动态对象。 一个对象总是对应了一块内存,对象的值就是内存里的数据。 对象生命期:全局对象生命期是永恒的,只有程序退出时才失效;局部对象生命期是临时的在超出作用域后对象立即消失;动态对象生命期是动态的,在malloc时生命生效,在free时失效。
4)常见问题 用malloc申请的内存,用完以后要用free释放。 不适用malloc得到的内存不能用free释放。 及时归还,再借不难。 不能只free一部分。 程序退出时,malloc内存都会自动释放归还给MM。***链表^概念 把若干对象用指针串联起来,形成一个链状数据结构,称为“链表”。 struct Student { int id; char name[16]; Student* naxt; } 其中添加一个成员变量next,用于指向下一个对象。
^链表的构造
1)先准备好四个对象 Student ss[4]= { {201501,"John",0}, {201502,"Jennifer",0}, {201503,"Anxi",0}, {201504,"Unnamed",0} };
2)把这个对象“串“起来 ss[0].next = &ss[0]; ss[1].next = &ss[1]; ss[2].next = &ss[2]; ss[3].next = 0; 一个链表构造完毕。
3)头节点与末节点 当若干个对象被串起来以后,只要知道第一个对象,就可以访问链表中的每一个对象。把链表中每个对象,称为“节点”。第一个节点也叫“头节点”
4)链表头的作用:可以用于代表整个链表:Student* stu_list = &ss[0];
^有头链表
1)概念:用一个固定的头节点来指代整个链表,所有的对象挂在这个头节点下面,而头节点本身并不包含有效数据。2)定义一个有头链表 只需要定义一个对象作为其节点,将成员next初始化为NULL。 Student m_head = {0,"head",NULL}; 或者写: Student m_head = {0}; 当有对象加入时,直接加在后面就可以,当他的next为NULL时表示该节点没有数据节点(链表长度为0)。
3)添加一个节点 void add(Student* obj) { obj->next = m_head.next; m_head.next = obj->next; } 创建一个对象,然后调用add函数插入列表中。 Student* obj = (Student*)malloc(sizeof(Student)); obj->id = 12; strcpy(obj->name,"X"); add(obj); 上面的add函数直接把新的节点插在最前面,也可以把节点插到末尾,代码如下: void add(Student* obj) { Student* p = &m_head; while(p->next) p = p->next; p->next = obj; obj->next = NULL; }
4)有头链表的遍历 在遍历时,有头链表的头节点由于不含有数据,是不参与遍历的实际遍历时,只访问链表中的数据节点。 void show_all() { Student* p = m_head.next; while(p) { printf("ID: %d,name: %s\n",p->id,p->name);p = p->next; } }
5)按顺序插入节点 先遍历链表,并比较ID的值,找到目标位置,并记录前一个节点为pre,找到位置后,把新的节点直接挂在pre后面。 obj->next = next->next; pre->next = obj;6)查找和删除节点***引用^定义 在类型之后加上一个&符号,该变量即为引用类型。 引用只相当于对象的一个别名。
^与指针的区别 引用在定义时必须初始化关联到一个对象,例如:Object* p = NULL; //允许在定义的时候不指向任何一个对象,如果在定义一个引用时不初始化,则编译器就会报错,例如:Object a; Object& r; //语法错,定义引用时必须初始化! 引用与某个对象绑定,中途无法解绑,而指针的使用较为灵活,一个指针可以先指向对象a,再指向对象b,引用可以视为功能受限的指针。
^简单的例子^作为函数参数
引用类型可以作为函数的参数,可以达到与指针相同的效果。
^做为函数返回值
引用也可以作为函数返回值,指针作为返回值是把某个对象的地址返回,^const引用 const引用限定被引用的对象为只读的,不能修改的对象,常用作函数的参数。***字符串^字符串的三种形式
1)字符数组 当以char型数组来存放字符串时,数组名时字符串的首地址。
2)动态字符串 可以动态分配一块内存,然后在这块内存里存放一串字符。也就是说,这个字符串对象在堆上。
3)字符串常量 在代码中用双引号包括,包含0..N个字符,称为字符串常量。^字符串常量多行表示 当一个字符串常量要表达的内容特别长时,在单独一行代码中可能书写不下,可以用两种方法分成多行。
第一种:使用双引号将多段文本连接起来,两个字符串之间可以被空格分开,不影响最终效果。例如 const char* str = "hello" "world";相当于:const char* str = "helloworld";
第二种:在末尾添加一个反斜线,例如: const char* poem = "江山定\n\ 风雨遮前路,冰火伴我行.\n\ 一度波澜惊,而今江山定.\n\;
^字符串与与普通数据
字符串是以char*表示的,它指向了字符串的首地址。实际上仅当这块内存用于存储字符串的时候,才把它称作字符串,如果只是把它用于存储一些普通数据则不能把它称作字符串。
^字符串的遍历
遍历字符串指的是从前往后访问每一个字符,有两种方法:索引遍历和指针遍历。都需要检测结束符'\0'来判断是否结束。
^字符串长度
是指从第一个字符开始,一直到末尾的结束符,中间的有效字符的个数,长度不包含末尾的'\0'在内。^字符串复制 字符串的复制,是指将源字符串的每一个字符挨个复制到目标缓存区,最终保证目标缓冲区的字符串末尾有一个'\0'字符。
注意事项:目标缓冲区要足够大;目标缓冲区保证以0结束。可以使用里的strcpy函数。
区分深拷贝和浅拷贝:例如 char* p1 = "hello,world"; char* p2 = p1; 这种简单的这振赋值,就叫做浅拷贝,一句话表示:两个指针指向了同一个字符串对象。 char* p2 = (char*)malloc(strlen(p1)+1); strcpy(2,p1); 这段代码新申请一块相同大小的内存,然后把字符串内容复制到这块内存,那么这两个字符串对象(两块内存),他们的内容相同,这种拷贝叫做深拷贝,一句话表示:两个指针(p1,p2),分别指向两个字符串对象。
^字符串比较
字符串也可以比较是否相等,以及大小关系,只有当所有字符全部相同时才认为两者相等。两个字符则是按它们的ASCII码值大小进行比较。
一般直接使用里的strcmp函数来比较两个字符串。strcmp(a,b),当相等时返回值为0,当ab时返回值为1.
^字符串插入与删除
1)删除字符
函数Erase用于字符串中某个字符的删除。
2)插入字符
函数Insert用于在源字符中插入一个字符。
^字符串分割
一个字符串由若干信息组成,每一段信息中间用分隔符分开,解析这个字符串,得到一段内容称之为字符串的分割。
^用数组还是指针
数组方式:
指针方式:
**标准C函数库
1)stdio.h
标准输入/输出函数
2)math.h
3)time.h
4)stdib.h
5)string.h
***文件操作
^认识文件
文件的作用是持久化存储数据。所谓持久化是指当关闭计算机电源后数据依然存在,再次打开计算机时,还可以重新加载显示这些数据。
^保存数据
使用ANSI C中的stdio.h里的相关数据来进行文件读写操作。步骤如下;
1)fopen:打开文件。
fopen函数用于打开文件,得到一个FILE*指针,该指针指代该文件,后续的fwrite/fclose等函数都需要传入这个文件指针。其原型为:
FILE fopen(const char *filename,const char *mode);
其中,filename:表示要打开的文件路径;mode:固定使用"wb"(w表示write,b表示binary);
用法示例:FILE* fp = fopen("c:/aaa.txt","wb");
if(fp == NULL)
{
printf("文件打开失败\n");
}
2)fwrite:写入数据。
当数据写入完毕,该文件不再被指针使用时,要及时调用fclose函数来关闭文件,其原型为:
int fclose(FILE* stream);
参数:stream就是前面fopen的返回值。用法示例:
fclose(p);
3)fclose:关闭文件。
fwrite用于向文件中写入数据,其原型为:
size_t fwrite(const void * buf,size_t size,size__t nelem,FILE * stream);
参数:stream就是前面fopen的返回值;buf:要写入的数据首地址;size:总是传1;nelem:数据长度。用法示例:
char buf[] = "hello";
fwrite(buf,1,5,fp);
^读取数据:就是把曾经写入的文件读取出来。
读取数据分为三步。
1)fopen打开文件
2)fread读取文件
fread函数原型为:
size_t fread(void * buf,size_t size,size_t nelem,FILE * stream);
参数:stream:前面fopen函数的返回值;buf:内存缓冲区,用于存储数据的内存位置;size:恒为1;nelem:最多读取多少字节;返回值:实际读取字节,如果
返回-1,则读取失败。用法示例:
char buf[128];
int n = fread(buf,1,128,fp);
如果文件中的数据不超过128字节,则返回值n就是实际的字节数。如果文件中的数据超过128字节,那这次操作只能读取128个字节。
3)fclose关闭文件
^数据的存储格式
第一种方式:
int x = 100;
int y = 100;
fwrite(&x,1,2,fp);
fwrite(&y,1,4,fp);
当以这种方式写入时,一共8个字节。可以用相应的代码,从文件中读取数据,并恢复x,y坐标。
int x,y;
fread(&x,1,4,fp);
fread(&y,1,4,fp);
第二种方式:
int x = 100;
int y = 200;
char buf[128];
sprintf*(buf,"x = %d,y = %d",x,y);
fwrite(buf,1,strlen(buf),fp);
这种方式把一个字符串“x = 100,y = 200”写入文件。
第三种方式:
int x = 123;
int y = 456;
char buf[128];
sprintf(buf,"%d%d",x,y);
fwrite(buf,1,strlen(buf),fp);
^存储格式:按字节存储
1)存储char类型
char ch = 12;
fwrite(&ch,1,1,fp); //存
fread(&ch,1,1,fp); //取
2)存储int类型
int n = 12;
fwrite(&n,1,sizeof(int),fp); //存
fread(&n,1,sizeof(int),fp); //取
3)存储double类型
double val = 123.456;
fwrite(&val,1,sizeof(val),fp);
fread(&val,1,sizeof(val),fp)
4)存储结构体类型
Object obj = {123};
fwrite(&obj,1,sizeof(obj),fp);
fread(&obj,1,sizeof(obj),fp);
5)存储字符串
char name[32] = "shaofa";
fwrite(name,1,32,fp);
fread(name,1,32,fp);
^存储格式:文本化存储
当数据量比较少时,可以把数据格式化为文本的形式来存储。
1)fprintf按行格式化写入
2)fgets按行读取
^文件的随机访问
在描述一个文件的可访问属性时,有两个术语。
顺序访问:不能跳跃。
随机访问:随意跳到一个位置访问。
1)fseek
2)文件位置指示器
3)随机访问实例
4)fseek的物理限制
5)文件被重复打开的情况
^文件打开模式
rb:读模式。当读一个文件使用。如果该文件不存在,则fopen返回NULL.
wb:写模式。再写一个文件时使用。
ab:附加模式。表示打开文件但不清空里面的内容。
***多文件项目及编译过程
^extern
1)extern声明全局函数
想要在main.cpp中调用其他.cpp中定义的函数,那么就必须在main.cpp里用extern声明这个函数,写法如下:
extern double get_area(double r);
以关键字extern修饰,在后面加上函数的原型,关键字extern不仅可以声明一个外部函数,还可以声明一个外部的全局变量。在声明全局时,关键字extern是
可以声明不写的。
2)extern声明全局变量
也可以在A.cpp里访问B.cpp里的全局变量,需要在A.cpp里用extern声明这个全局变量。
注意:在声明变量时不能加初始值;必须要在前面加上extern。
3)深入理解全局变量
extern的作用是通知编译器在本cpp中要用到某个符号,这个符号可能不在本cpp中定义,它表示在某个cpp文件中存在这么一个全局变量/函数,这个符号可以
再别的cpp中定义,亦可以在本cpp中定义
^多文件项目的生成
1)第一阶段:编译
编译,这一阶段是处理每个cpp文件,把cpp文件中的代码转换为中间文件(*.obj),可以再debug文件夹中找到这个中间文件,A.cpp->A.object,C.cpp->B.obj。
在变异过程中各个cpp文件都不区分顺序,谁先谁后都一样,只要声明了一个函数为extern,编译器就不检测是否真的存在这个符号。
2)第二阶段:链接
如果迁移阶段编译成功,则进行连接过程。此过程作用是将各个obj文件综合在一起,生成可执行程序。A.obj,B.obj,..->test.exe。
在连接阶段编译器会检测所有extern的福海是否真的存在。
3)用伪代码表示整个过程
可以用一段伪代码来表示编译器和连接过程,并非真正的c++代码,仅用于解释说明。
4)全量编译与增量编译
全量编译是将所有的cpp文件重新编译一下,“重新生成解决方案”就是全量编译。
增量编译是指对有改动的文件进行执行,当执行生成解决方案时执行的是增量编译,这也是大多数编译器的默认动作。
^头文件#include指令
头文件的后缀名一般为.h,相应的把.cpp文件叫做头文件。
1)为什么需要头文件
同一结构的定义要在不同的cpp里重复好几遍。如果不写,就是语法错误。由于每个cpp时独立编译的,在other.cpp中定义的结构类型对main.cpp没有任何
影响
2)使用头文件
吓死项目中新增一个头文件Objeect.h,然后把Object的类型定义写在里面,然后在需要它的cpp里加上#include"Object.h",这样就解决了前面所说的问题,需
要扩展Object结构时,只需要修改Object.h即可。头文件写法:后缀一般为.h;内容一般为几种:类型定义,extern函数声明,extern变量声明。
3)#include指令的原理
#include"Object"
其中,#include称为一条“预处理指令”。"Object.h"表示要包含的头文件的路径,以双引号包围。预处理过程是:编译器过程的作用是,编译器在处理每个cpp
之前,首先将文件里的所有预处理指令进行处理,形成一个中间文件,然后对这个中间文件进行编译。
4)头文件的重复包含问题
5)头文件里的内容
头文件里,一般放以下内容:
公用类型定义:如果一个类型要在多个cpp中使用,可以放在头文件里。
extern函数声明:extern变量声明。
嵌入包含其他文件。
^宏定义#define指令
所有已#开头的行,称为预处理指令。#define指令通常称为宏定义。两个用法:
1)#define的一个数值
使用#define可以起到定义一个“常量”的效果。
2)#define的一个算式
使用#define可以定义一个类似“函数”的东西,它不是函数。学这些东西只是为了能够看懂一些老旧的代码。
3)几个常见宏定义
NULL空指针:#define NULL 0
RAND_MAX的宏定义:见16章。
^条件编译指令
1)#if...#endif
2)#ifdef...#endif
#ifdef表示如果对应的宏有定义,则相应的代码被编译。可以使用#undef指令去定义。
#ifndef表示的意思和#ifdef恰好相反:当相应的宏不存在时,才编译相应的代码。
3)结局头文件重复包含的问题
通常要对头文件用条件编译指令对其进行保护,之后便可以对其重复包含了。
^main函数的参数和返回值
1)main函数的参数
2)main函数的返回值
^static的用法
1)static修饰变量
2)static修饰函数
***面向对象编程
^面向对象设计的过程
^实例演示
^封装
***类
^类和成员变量
^类和成员函数
^变量名字和覆盖
^命名规范
^类的封装
^类的分离式写法
^const对象与const函数
***构造与析构
^构造函数
^析构函数
^自动生成构造/析构函数
^默认构造函数
^混合使用两种初始化方式
^构造与析构的顺序
^分离式写法
^无名对象
^构造函数与类型转换
***动态创建对象
^回顾malloc/free
^用new和delete创建/销毁对象
^new/delete与malloc/free的区别
^为new指定初始化对象
^默认构造函数的必要性
^注意事项
***继承
^继承的概念
^访问修饰符protected
^成员函数重写
^虚拟继承
^虚函数virtual
^继承关系下的构造与析构
^多重继承
^继承函数与纯虚类
^以protected/private方式继承
***拷贝构造函数
^定义
^拷贝构造函数的调用
^默认拷贝构造函数
^定义拷贝构造函数
^深度拷贝
***静态成员
^static定义全局变量
^static定义全局函数
^与普通成员函数的区别
^static语法的特点
^实例
***朋友成员
^类的朋友
^friend的语法
^实例
***重载操作符
^算术操作符
^赋值操作符=
^自增操作符++与自减操作符--
^关系操作符
^逻辑操作符
^类型转换操作符()
^元素操作符
^输入/输出操作符>>与<<
^操作符new与delete
***内部类和名字空间
^内部类
^名字空间
**模板
^函数模板
^类模板
^模板参数
^实例
***标准函数库
^一般使用方法
^向量vector
^list
^string
^map
^stack
^queue
***异常
^