C和C++的区别:
C语言是C++的子集,C++是 C语言的超集。C++是 C语言的 OOP版本
一.数据类型
C++中新的数据类型:bool
逻辑类型 | 真 | 假 | |
C | 没提供 | 非0 | 0 |
C++ | bool | true | false |
写法对比: C++中增加了布尔类型,使程序简洁易读
C语言写法 | C++写法 |
int flag = 0; if(flag == 1) { //to do }else{ //to do } | bool flag = 0; if(flag){ //to do }else{ //to do } |
nullptr 和 NULL :
nullptr :是c++11中的关键字,是字面值常量,表示空指针,类型为std::nullptr_t, 空指针常数可以转换为任意类型的指针类型。
NULL : 是一个宏定义,在C和C++中的定义不同,C中NULL为(void*)0, 而C++中NULL为整数0:
//C语言中NULL定义
#define NULL (void*)0 //c语言中NULL为void类型的指针,但允许将NULL定义为0
//C++中NULL的定义
#ifndef NULL
#ifdef _cpluscplus //用于判定是c++类型还是c类型,详情看上一篇blog
#define NULL 0 //c++中将NULL定义为整数0
#else
#define NULL ((void*)0) //c语言中NULL为void类型的指针
#endif
#endif
C语言基础:
scanf : 传的是变量地址; 当有多个值时,以回车或空格分开
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
常量指针和指针常量
//常量指针 : const在*的左边,'常量'(即值)不能变
//const int* n_p = &number2;
int const* n_p = &number2; //跟上面一样的.
n_p = &number1;
//指针常量 : const在*的右边,指针(的地址)不能变
//int * const n_p = &number2;
int * const n_p = &number2;
*n_p = 300;
数据类型:4类:
基本类型: 有 数值类型(整型(3类)和浮点型(2类))和字符类型
构造类型: 有数组、结构体、共用体、枚举
指针类型
void空类型
野指针:野指针不是NULL指针,通常NULL指针可以使用if语句来判断,但C语言中没有任何方法用来判断一个指针是否野指针。
指针变量的本质: 是值,这个特殊的值是一个内存地址值.
合法的内存地址:包括定义的变量的地址(栈)、malloc函数申请堆内存返回的地址(但未使用free释放,是在堆空间动态申请).
原因:野指针是因为指针变量中保存的值不是一个合法的内存地址或者指向不可用内存的指针而造成的。
后果:野指针往往会造成内存越界、段错误等难以找到的问题。
赋值语句前面没有类型,否则是定义变量时顺带初始化。
把一个字符常量赋值(/放到)一个字符变量中,实际上并不是把字符本身放到
字符变量所属的内存中,而是把字符对应的ASCII码(0-127间的一个数字)存放到内存中。
char c1, c2;
c1 = 97, c2 = 98;
printf("c1 = %c, c2 = %c\n", c1, c2);
printf("c1 = %d, c2 = %d\n", c1, c2);
ASCII码记忆: 'a'(97), 'A'(65)
一些特殊的写法:一个常数后加一个字母U(u)/L(l)/F(f),表示该常数用无符号整型方式存储。
但意义不大,因为常量一般都会赋值给一些变量,而实际的类型取决于左边的变量的类型。
int a = 23.12F; //变量a依旧是int型
字符常量和字符串常量:
'a'在内存中占1个字节, 而
"a"在内存中占2个字节,自动在a末尾添加一个 \0
注:在printf中如果出现'0',表示结束这个字符串的内容,后面的不会显示。如printf("abcdefg\0hij");
变量赋初值:就是在定义变量的同时给该变量一个值
定义变量时,不赋初值的变量中所保存的值是不确定的。所以不赋初值的变量,不应该拿来参与运算。
C语言中,变量一定要先定义后才能使用。
强制类型转换运算符,是将一个表达式转换成所需要的类型: (类型名)(表达式名)
//注:得到的是一个所需类型的中间变量,而原来变量的类型没有发生变化.
如:(int)(x+y)
自增自减运算符: 就近原则
自增和自减运算符只能用于变量,不能用于常量或者表达式:
如5++ 、 (a+b)++
赋值表达式本身是有值的,就是赋值运算符=右侧的值: a=(b=5);
赋值表达式可以被包含在其他表达式中: //但不建议这样写
printf("x=%d\n", x=8);
if ((a - b) < 0) t = a;
逗号运算符: 就是一个逗号
逗号表达式: 用逗号将两个表达式连接起来构成的一个更长的表达式:
表达式1,表达式2
//求解过程:先求解表达式1,再求解表达式2,整个表达式的值是表达式2的值 . 且遵循的是从左到右的顺序.
eg. a=(4,5); a=(3+5,6*8); a=3*5,a+4;
语句分类:
控制语句:9类, 主要区分和学习continue, break, goto(无条件转向语句)
函数调用语句
表达式语句
空语句: ; //作用不大,用于占位
复合语句: {...} //很少用到,一般用于测试等特殊用途
#include <> : 去系统目录寻找头文件
#include "" : 先到当前源代码文件所在目录下寻找,如果找不到,再到系统目录中寻找.
数据的输出:
putchar(char) : 向屏幕输出一个字符,只能是字符,且是一个
如: putchar('\n'); putchar('a'); putchar(97);
printf(格式控制字符串,输出列表); ===> 即: printf(参数1, 参数2, 参数3,..., 参数n); //用于向屏幕输出若干个任意类型数据
%d(十进制形式), %x(十六进制形式), %u(十进制unsigned形式), %o(八进制形式) %f, %.4f(4位小数), %c, %s(字符串), %%(输出百分号%)
数据的输入:
getchar() : 等待用户从键盘上输入一个字符,按Enter键后程序才会继续执行;
一般不常用,但有一个特殊用途: 在main函数的末尾使用, 用于防止命令行窗囗一闪而过.
scanf(格式控制字符串,地址列表); //用于从键盘输入任何类型的一到多个数据 ; 当有多个时,可通过tab键,回车键或空格键分离开
如: int a,b,c; scanf("请输入三个数:%d %d %d",&a, &b, &c);
//注:如果使用了其他符号分隔时,输入时也要用上对应符号分隔,否则报错.
#pragma warning(disable:4996) : 添加在#include头文件之后. 当有提示一些编译错误或系统认为某个函数不安全时,可用此来让编译器忽略一些警告信息,也可在项目的编译参数里设置.
关系运算符有: >= , <= 连着写的
goto(无条件转向语句) :
作用 : 与if语句一起构成循环结构
goto语句不能跨函数使用
while(表达式){循环体中要包含使循环趋向结束的语句}
do{循环体中要包含使循环趋向结束的语句}while(表达式) //区别是先执行一次循环体语句
for(表达式1;表达式2;表达式3){内嵌的语句}
注:for语句的执行顺序:
1. 求解表达式1的值;
2.求解表达式2的值;
3.若表达式2的值为true,则执行内嵌语句,同时求解表达式3,反复循环步骤2,直到表达式2的值为false就结束循环.
//for语句会有很多种花哨的用法,见P67.
数组:定义数组时,数组大小是固定的,只能是常量或常量表达式,不能是变量
对于一维数组,获取数组的size: int len = sizeof(arr) / sizeof(arr[0]);
数组和指针的区别 : 数组名也是一个指向数组首地址的指针,但这个指针应该是个 *const的,即不能通过数组名来++运算来移动指针,只读;
而指针可以++来移动位置
二维数组: float a[3][4]; //不能写成a[3,4],C#中才可以
二维数组的元素存放顺序是:按行存放; 把二维数组看成是一种特殊的一维数组 .
二维数组的初始化:
1.按行赋初值: int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
2.将所有数据放在一个大括号里: int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
3.对部分元素赋初值: 这样其他元素为0
int a[3][4] = {{1},{3,4}};
int a[3][4] = {{1},{},{9}};
int a[][4] = {{0,0,3},{},{0,10}};
4.若对全部元素赋初值,则定义时第一维的长度可不指定,但第二维的长度不能省略
int a[][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
同理字符数组也类似,一般使用最多就是一维字符数组:
char c[10] = {'I',' ','a','m',' ','h','a','p','p','y'}; //逐
char c[] = {'I',' ','a','m',' ','h','a','p','p','y'}; //初始值个数和预定的数组长度相同时,可省略数组长度
//char c[8] = {'I',' ','a','m',' ','h','a','p','p','y'}; //编译会报错
char c[12] = {'I',' ','a','m',' ','h','a','p','p','y'}; //初始值个数少于数组长度时,剩下的赋值为'\0'(是空格的转义字符),数值就是0
char c[] = {"I am happy"}; //使用字符串常来初始化数组
对于字符串,系统会自动给字符串末尾增加一个'\0'字符作为整个字符串的结束标记.
转义字符不是可见字符.
字符串处理函数:
puts(字符数组); //将一个字符串输出到屏幕,注意只能输出一个字符串; 很少使用此函数了,而用printf("%s\n",str);来替代了.
strcat(字符数组1, 字符数组2); //把2往1的末尾添加. //注:1.str1数组必须中够大; 2.'\0'后移
strcpy(字符数组1, 字符数组2); //把str2复制到str1, 覆盖了str1原来的内容. 末尾的'\0'也复制了.
//注:数组1必须是一个数组名,而数组1可以是数组名,也可以是字符串常量.
注: 不能用赋值语句将一个字符串常量或字符数组名直接赋给(另)一个字符数组名,如下代码是不合法的:
str1 = "China";
str1 = str2; //不合法,编译不通过,必须得用strcpy(,)来拷贝,而不能直接赋值
严格区分定义的时候初始化 和 赋值 这两个概念:
char str5[10] = "one1234"; //定义的时候初始化,合法
str5 = "one1234"; //赋值,非法,
赋值语句只能将一个字符赋给一个字符型变量或数组元素
char c1, a[5];
a[0]='A'; c1='i';
strcmp(字符串1,字符串2); //从左到右逐个字符比较其ASCII码值大小,直到出现不同字符或遇到'\0'为止
注:C语言中比较两个字符串不能写成 if(str1 == str2),因为这是进行地址的比较
strlen(字符数组); //计算字符串长度. 不包括字符串结束标记'\0'的, 每个汉字占两个字节.
注意与sizeof()的区别: sizeof()计算的是变量或类型所占的内存大小; 而strlen是计算字符串(实际内容的)长度.
函数:
main函数由系统来调用,其名字是固定的 //不要调用main,会产生意想不到的结果
函数不能嵌套定义, 每个函数都是平行和独立的. 但能嵌套调用
形参,实参 ; 单向传递 (个数要相等,类型要一致,按顺序对应,一一传递)
函数声明, 函数定义, 函数调用
函数嵌套调用 : 系统会给函数调用分配一些内存来保存这些信息(局部变量,函数参数,函数调用关系等)但分配的内存大小是固定和有上限的,超过了会崩溃和异常退出 .
函数的递归调用, 是一种很特殊的函数嵌套调用. 递归调用的出囗必须要有,否则会死循环.
优点:
缺点: 理解起来有难度; 如果调用层次太深,调用栈(保存函数调用关系等要用到的内存)可能会溢出,而结果的溢出与否不重要.说明不能此递归解决.
分为递归函数直接调用和递归函数间接调用
数组名作为函数实参: 数组名代表的是函数的首地址,所以传递的是地址,即形参和实参指向了同一段内存地址.
extern : 用于外部变量说明, 同一个文件中,外部变量说明可以有很多次(不分配内存)
static关键字的作用:
1.函数内部局部静态变量. 保持上次离开该函数时的值
2.在字义全局变量时前面使用static, 那么该全局变量只能在本文件中使用
3.在函数定义前加static,表示该函数只能在本文件中调用
编译阶段:
预处理: 宏定义#define,文件包含#include,条件编译#ifdef
编译: 词法分析,语法分析,目标代码生成,优化,typedef等
汇编: 产生.o(.obj)文件
链接阶段:
产生可执行文件
宏定义: 分不带参数的宏 和 带参数的宏定义
进行宏定义时,可引用已定义的宏
宏名的有效范围是#define之后到源文件结束,不能跨文件使用. 可以#include或重新定义
#define
#undef //终止宏定义
带参数的宏定义: #define 宏名(参数表) 被替换的内容
作用:用右边的"被替换的内容" 代替 "宏名(参数表)"
条件编译: #ifdef DEBUG 1
程序段1
#endif
#ifndef....#else...#endif
跨平台编译的应用:
#if _WIN32 //这个宏系统会定义,不需要自己定义
#elif __LINUX__
指针类型参数的作用 : 将一个变量的地址传到一个函数中
C语言中对字符串常量有特殊处理,开辟出一块专门的内存来存放,且这块内存是只读的. 不能修改里面的值.
且其地址在编译时就被确定下来了.
函数返回指针类型,就是返回一个地址; 如果返回函数内定义的局部变量的地址, 基本测试了没有问题,但是不安全的不稳定的,因为在退出函数时被系统回收的.
解决办法是使用全局变量来保存这个地址.
指针数组 : int *p[4]; //是一个数组,这个数组保存的是指针类型,共4个,每个数组元素可看成是一个int的指针变量.
比较适合用来指向若干个字符串: const char* pName[] = {"C++", "Java", "Python", "GO", "CSharp"};
指向指针的指针,即二级指针; 指针运算符*,是从右到在结合的,所以 int **p; 相当于*(*p);
指针变量可以指向NULL(空),表示不指向任何有效内容
//NULL是一个宏,被定义为0,同时系统会保证地址为0的这个单元不存放在效数据
void *型指针,表示万能型指针,代表能够指向任意类型数据. 可人为强转类型,但要两者所占用的字节数相同.
结构体成员运算符. ,指向结构体成员运算符->
结构体指针: 就是结构体变量的指针,用于指向该结构体变量所占据的内存的起始地址.
通过结构体变量指针来访问结构体变量的成员,有两种方法:
1.通过结构体成员运算符.
2.通过指向结构体成员运算符->
枚举:
typedef : 关键字,用来定义新的类型名以代替已有的类型名(注:不是用来定义变量的)
可以用typedef定义一个结构体类型
typedef是编译时处理的.
作用: 有利于程序的通用性与可移植性,还能简化书写.
位运算: 6个位运算符:&,|,^,~,<<,>>
应用: 算法和密码法用得最多
异或^位运算的应用: 二进制位,想翻转的位和1做异或,想保留的位和0做异或
左移和右移运算:
unsigned int leftshiftvalue = 15<<1; //每左移1位就相当于乘以2
unsigned int rightshiftvalue = 15>>2; //每右移1位就相当于除以2,超出最低位的被舍弃
位运算符和赋值运算符可以结合使用.
EOF : 即-1,用来判断到达文件结束,十六进制是FF
OD代表回车符'\r', OA代表换行符 ’\n'
C++知识点:
static :
1.非类内函数的局部变量; //可不用赋初值,因为会默认初始化0
2.类内成员 : 解决同一个类的不同对象之间数据和函数共享问题
a.静态数据成员 : 静态数据成员定义和初始化,使用类名限定.且必须在类定义外进行
b.静态函数成员
常量,const关键字 :
const int var = 17;
int &var2 = (int&)var;
var2 = 15; //debug时修改到了var,但编译器又改回去了
///引用类型的const用法
int i = 100;
const int &a = i; //表示a代表的内容不能修改
//a = 200; //报错,不允许修改
const int &b = 156; //可以(字面值初始化常量引用)
//b = 157; //报错,不允许修改
//int &c = 156; //错误;
常量,constexpr关键字(C++11引入的),意思是在编译的时候求值,能提升运行时的性能; 但有一些限制
auto关键字(C++11引入) : 变量的自动类型推断,发生在编译期(所以不会造成运行时的效率问题).
主要应用:泛型里有些类名很长,使用auto避免书写很长的类型名
判断变量的类型:
auto i = 10;
cout<<typeid(i).name()<<endl;
头文件防卫式声明,防止(头)文件多次被#include
#indef __HEAD__
#define __HEAD__
int g_globalh1 = 8;
#endif
C++11变量的初始化
int b{5};
int b={5};
for(int i{0};i<10;i++){...}
int arr1[]{11,12,13};
int arr2[] = {11,12,13};
for(auto &x : {11,12,13}){ //使用引用的方式,避免数据的复制动作.{11,12,13}是一个元素序列
cout << x << " ";
}
一个容器只要基内部支持begin和end成员函数用于返回一个迭代器,能够指向容器的第一个元素和末端元素
的后面,主容器就可以支持范围for语句
strcpy_s(,,) : 是strcpy的安全版本,能够检测所容纳的元素是否越界,如越界会停止程序运行并弹出警告窗囗.
C++中,把内存分成5个区域:栈、堆、全局/静态存储区、常量存储区、程序代码区
malloc()和free()函数: 在C语言中,是系统提供的函数,用于从堆(空间)中分配和释放内存
int *p = NULL;
p = (int *)malloc(10*sizeof(int));
if(p != NULL){
*p = 5;
cout << p << endl;
free(p)
}
new和delete关键字 : new和delete不但做了malloc和free同样的事情---分配和释放内存,还做了更多的事情
int *a = new int;
int *b = new int(10); //分配内存的同时把内容设置为18
delete b;
int *c = new int[10]; //注:这里的10是数组的个数
delete[] c;
使用说明:
1.必须要配对使用,有malloc成功必有free,有new成功必有delete.
2.free/delete不能重复调用,因为free/delete后的内存可能被系统立即回收后再利用,现次调用就会把不是自己的空间也释放子.
3.new不但分配内存,还会额外做一些初始化工作;
delete不但释放内存,还会额外做一些清理工作.
nullptr : C++11中的关键字,表示"空指针"
char *p = NULL; //NULL是0
char *q = nullptr;
int *a = nullptr;
if (p == nullptr) { //条件成立
cout << "nullo" << endl;
}
cout << typeid(NULL).name() << endl; //int
cout << typeid(nullptr).name() << endl; //std::nullptr_t
NULL和nullptr的不同点 : 函数重载时使用不同,更多不同点见后续
结论:
1.对于指针的初始化,能用nullptr的全部用nullptr;
2.以往用到的与指针有关的NULL的场合,能用nullptr取代的全部用nullptr取代.
C++中的结构体与C中的结构体:
1.声明变量果,可省去struct关键字(C中必须要有,除非使用typedef);
2.可以定义成员函数(方法);
3.增加了访问修饰(有3种),且缺省情况下是public
C++中的类和结构:
类中,缺省的访问权限是private
类-对象(而C语言只有结构和结构变量).
C++中的核心是类,一般使用类来实现功能
C++11引入后置返回类型:
auto func(int, int)->int;
auto func(int a, int b)->int{
return 1;
}
内联函数inline,编译期完成,内的结果取决于编译器
constexpr函数可看成更严格的一种内联函数,因为constexpr自带inline属性
函数重载, 注:const关键字在比较同名函数时会被忽略的.
auto关键字(C++11引入) : 变量的自动类型推断,发生在编译期(所以不会造成运行时的效率问题).
主要应用:泛型里有些类名很长,使用auto避免书写很长的类型名
判断变量的类型:
auto i = 10;
cout<<typeid(i).name()<<endl;
头文件防卫式声明,防止(头)文件多次被#include
#indef __HEAD__
#define __HEAD__
int g_globalh1 = 8;
#endif
C++11变量的初始化
int b{5};
int b={5};
for(int i{0};i<10;i++){...}
int arr1[]{11,12,13};
int arr2[] = {11,12,13};
for(auto &x : {11,12,13}){ //使用引用的方式,避免数据的复制动作.{11,12,13}是一个元素序列
cout << x << " ";
}
<<(往左扎)是一个输出运算符, std是命名空间, cout是一个与iostream相关的对象,称为"标准输出"
cout<<x<<endl; //把<<右侧的内容写到cout(屏幕)中去
其原型是: ostream& std::cout.operator<<(); //返回的是一个写入了(右侧)给定值的cout对象
std::endl是一个函数模板名,相当于函数指针,可理解成函数; 一般用在函数的末尾,有2个作用:
1).输出换行符
2).刷新输出缓冲区,调用flush(理解成函数)强制输出缓冲区中所有数据(也中刷新输出流,目的是显示到屏幕),然后把缓冲区中的数据清空.
//输出缓冲区: 理解成一段内存,数据临时保存到输出缓冲区,然后一次性地将这些数据定入硬盘.
<<是右结合性: 如 std::cout<<i--<<i--; //末尾的i--先计算
std::cout<<x<<"的平方是"<<std::endl; 相当于 ((std::cout<<x)<<"的平方是")<<std::endl; //因为右结合性,所以可省略()
同理, cin是标准输入对象, 运算符>>(往右扎),扎向变量,表示把值传递给变量. std::cin>>endl;
cout<<不能输出string对象,只能通过.c_str()转为char*来输出;
一个容器只要基内部支持begin和end成员函数用于返回一个迭代器,能够指向容器的第一个元素和末端元素
的后面,主容器就可以支持范围for语句
strcpy_s(,,) : 是strcpy的安全版本,能够检测所容纳的元素是否越界,如越界会停止程序运行并弹出警告窗囗.
C++中,把内存分成5个区域:栈、堆、全局/静态存储区、常量存储区、程序代码区
malloc()和free()函数: 在C语言中,是系统提供的函数,用于从堆(空间)中分配和释放内存
int *p = NULL;
p = (int *)malloc(10*sizeof(int));
if(p != NULL){
*p = 5;
cout << p << endl;
free(p)
}
new和delete关键字 : new和delete不但做了malloc和free同样的事情---分配和释放内存,还做了更多的事情
int *a = new int;
int *b = new int(10); //分配内存的同时把内容设置为18
delete b;
int *c = new int[10]; //注:这里的10是数组的个数
delete[] c;
使用说明:
1.必须要配对使用,有malloc成功必有free,有new成功必有delete.
2.free/delete不能重复调用,因为free/delete后的内存可能被系统立即回收后再利用,现次调用就会把不是自己的空间也释放子.
3.new不但分配内存,还会额外做一些初始化工作;
delete不但释放内存,还会额外做一些清理工作.
nullptr : C++11中的关键字,表示"空指针"
char *p = NULL; //NULL是0
char *q = nullptr;
int *a = nullptr;
if (p == nullptr) { //条件成立
cout << "nullo" << endl;
}
cout << typeid(NULL).name() << endl; //int
cout << typeid(nullptr).name() << endl; //std::nullptr_t
NULL和nullptr的不同点 : 函数重载时使用不同,更多不同点见后续
结论:
1.对于指针的初始化,能用nullptr的全部用nullptr;
2.以往用到的与指针有关的NULL的场合,能用nullptr取代的全部用nullptr取代.
C++中的结构体与C中的结构体:
1.声明变量果,可省去struct关键字(C中必须要有,除非使用typedef);
2.可以定义成员函数(方法);
3.增加了访问修饰(有3种),且缺省情况下是public
C++中的类和结构:
类中,缺省的访问权限是private
类-对象(而C语言只有结构和结构变量).
C++中的核心是类,一般使用类来实现功能
C++11引入后置返回类型:
auto func(int, int)->int;
auto func(int a, int b)->int{
return 1;
}
内联函数inline,编译期完成,内的结果取决于编译器
constexpr函数可看成更严格的一种内联函数,因为constexpr自带inline属性
函数重载, 注:const关键字在比较同名函数时会被忽略的.
vector是一个标准库中的类型,代表一个容器、集合或者动态数组这样一种概念
#include<vector>
//vector<int &> vj; //错误,vector不能用来装引用.(引用只是一个)
put_back(.)
其实vector这是一个类模板,后面接<>实际上是类模板的一个实例化过程
迭代器 : 是一种遍历容器内元素的数据类型
常用的容器vector,list,map等,
C++标准库为每个容器都定义了对应的一种迭代器类型. 有很多容器不支持[]操作,但支持迭代操作.
迭代器的失效, 所以在for循环中不要改变vector对象的容量.
构造函数声明中含explicit表示只能显式初始化
Time myTime ={}; //不行, 因为是隐式初始化
Time myTime{12,13,52}; //可以,因为是显式初始化(又叫直接初始化)
构造函数初始化列表: Time::Time(int tmpHour,int tmpMin,int tmpSec):Hour(tmpHour),Minute(tmpMin)
在函数体执行之前就执行了,对于内置类型区别不大,但对于类类型的成员变量,效率更高(因为少调用了一次甚至几次该成员变量的相关类的各种特殊函数,如构造函数等)
平常的中函数体内赋值
常成员函数: 在成员函数的声明和实现 ,在末尾增加const,表示在这个成员函数中不会修改到类对象里的任何成员变量的值.
void method() const
{
}
this在成员函数里面使用,其实是隐藏起来的一个函数参数,
编译器自动重写方法: Time& Time::rtnhour(Time *const this, int tphour){...} //由此可见this是一个指针常量,是个常量,总是指向这个对象本身,而不能指向其他地方
析构函数:~ClassName(){},没有返回值,不接受任何参数,不能被重载(即一个类,只有唯一一个析构函数)
如果不写自己的析构函数,编译器可能会生成一个默认析构函数,也可能不会生成一个默认析构函数,取决于具体需要。
在什么情况下有必要写自己的析构函数:
在构造函数时new了一段内存,一般应该要写自己的析构函数,在析构函数里面delete掉释放内存,否则内存泄漏。
友元 : 核心思想是'不相关',
友元函数: 类内成员对于这个函数相当于public,即类内任何成员的地方都能访问;
主函数main也可声明为友元函数
说明友元函数跟嵌套在它的类是不相关的,同理类也可声明为不相关的类(即友元类)
友元类:在友元类的内部定义里,可访问它的声明类里的私有方法
友元的特点:
不受访问修饰符的影响
可以有多个友元
缺点:破坏了类的封装性,不是迫不得已已,不要用
class A{
private :
int count;
friend void frientFunc();
friend int main();
friend class ClassB;
void privateFunc(){
count = 12;
cout << count << endl;
}
};
class B{
A a;
void func2(){
a.privateFunc();
}
};
int main(){
A a;
a.privateFunc(); //因为main被声明为友元函数,所以在友元函数内可通过对象访问私有成员
B b;
b.func2();
friendFunc(); //main中可访问友元函数
}
虚函数、纯虚函数、抽象类(没有abstract关键字,只要有纯虚函数就是抽象类,不能被实例化)
线程:C++ 11,语言本身支持线程
std::thread thObj(函数名);
thObj.join(); //阻塞线程,等待‘函数名’子线程执行完毕,才执行join语句后面的
thObj.detach(); //脱离,与主线程脱离,即后台线程,即不可控
一旦调用了detach,就不可以再调用join了
创建线程的三种方式: 详见
1.直接使用函数名: thread obj(函数名);
2.用类来创建线程,类中定义operator()()方法, 即重载()运算符,将类对象名传输到thread类构造函数中后。//实际上时调用了自定义类的拷贝构造函数复制了一份,线程执行结束后会调用自定义类的析构函数。
3.用Lambda表达式来创建线程:
auto lambObj = []{};
thread obj(lambObj);
线程扩展:
1.子线程调用join()函数时,可以先使用joinable()函数判断是否能够加入,一个子线程只能加入一次,若能加入则返回true,否则返回false.
2.当有多个子线程需要同时进行时,最好将子线程的join()函数调用放到所有子线程的后面,这样能确保子线程之间不会等待
---------------------------------0307学习-----------------------------
复杂类型分析:
原则:从变量名处起,根据运算符优先级结合,一步一步分析
结合率:[]的优先级比*强
普通类型、指针、引用的传参和返回:
//不要返回堆对象的拷贝,要返回到栈对象的拷贝
MyClass func1
{
MyClass c1;
return c1;
//不建议,会内存泄露,因为没有机会释放; 除非在某个地方,保存、释放
MyClass *pc2 = new MyClass();
return *pc2
}
一般程序,函数返回值,慎用返回指针:只能返回堆对象的指针
返回引用,其实也不推荐,但也有特殊情况
传参推荐const引用
栈:是编译时确定的,静态的
堆:具有灵活性,用多少分配多少,有全局性
没有栈就没有函数了,没有栈就没有变量
page fault : 缺页(异常)
page fault delta :看它最近的情况
累计值
new 出来后以分配好地址,内存管理器,写入物理内存
影响功耗。
引用在底层就是指针,速度上和指针一样的快。
建立良好的习惯,尽量传引用,如果不想让函数里面修改到引用的值,加上const
如果只有1-2个字节,也可使用传值
返回值传递:也尽量使用引用,尽量是指在可以的情况下
正规的防卫式声明:(写的任务的头文件,最好都加上防卫式声明
complex.h
#ifndef __COMPLEX__
#define __COMPLEX__
...
...
#endif
template<typename T>
class complex{
public:
complex(T r=0, T i=0):re(r),im(i){}
complex& operator +=(const complex&);
T real() const{return re;}
T imag() const{return im;}
private:
T re, im;
friend complex& __doapl(complex*, const complex&);
};
//使用
{
complex<double> c1(2.5, 1.5);
complex<int> c2(2, 6);
}
不带指针的类,多半不需要释构函数