C++语言
第一个c++程序
#include<iostream> //输出输出流 int main() { //std 命名空间 //cout 对象 ostream //<<重载运算符 //endl 换行+刷新缓冲区 std::cout<<"hello world!"<<std::endl; }
std::
std::是个命名空间标识符
将cout放到命名空间std中,是因为cout这样的对象在实际操作中可能会有许多个,如果不小心定义了一个对象名为cout,那么这两个cout对象就会产生冲突。
#include<iostream> using namespace std;//不推荐 int main() { cout<<"hello world!"<<endl; }
#include<iostream.h>//非标准 int main() { cout<<"hello world!"<<endl; }
#include<iostream> using std::cout; using std::endl; int main() { cout<<"hello world!"<<endl; }
命名空间 namespace
C++ 引入命名空间(namespace) 的目的是为了减少和避免命名冲突。
#include<iostream> namespace a { int b = 5; int c; } namespace c { int b = 6; } int b; int main()//:: 作用域符 { //::b; int b = 9; std::cout<<b<<" "<<a::b<<" "<<c::b<<std::endl; }
变量
作用:给一段指定的内存空间起名,方便操作这段内存
语法:数据类型 变量名 = 初始值
示例
变量的意义:方便管理我们的地址空间
常量
用于记录不会更改的量
c++定义常量的两种方式
1.#define 宏定义 #define 常量名 常量值 定义在文件上方,表示一个常量 2.const 修饰的变量 const 数据类型 常量名 = 常量值 通常是不可修改的
标识符
作用: c++规定给标识符(变量、常量)命名时,有一套自己的规则
1.标识符不能是关键字 2.标识符只能是字母、数字、下划线组成 3.第一个字节必须是字母或者下划线 4.标识符中字母区分大小写
建议:给变量名起名的时候最好做到见名之意
数据类型
数据类型存在的意义: 给变量分配合适的内存空间
1.整形
语法 数据类型 变量名 = 变量初始值
作用:整型变量表示是整形类型的数据
c++中能够表示整形的类型有几种方式,区别在于所在内存空间不同:
数据类型 占用空间 取值范围 short 2字节 -2^15 ~ 2^15-1 int 4字节 -2^31 ~ 2^31-1 long 4 linux:8 -2^31 ~ 2^31-1 long long 8字节 -2^63 ~ 2^63-1
2.sizeof关键字
作用:利用sizeof关键字可以统计数据类型所占用的内存大小
语法:sizeof(数据类型/变量)
3.实型(浮点型)
作用:用来表示小数点
浮点数变量分为两种:
1.单精度 –float
float f1 = 3.14f
2.双精度 –double
double f2 = 3.14
科学计数法
float f2 = 3e2 //3*10^2
4.字符型
作用:字符型变量用于显示单个字符
语法:char ch = ‘a’;
注意1:在显示字符变量时候,用于引导将字符括起来,不要用双引号
注意2:在引导内只有一个字符,不能用双引号
5.转义字符
作用:用来表示一些不能显示出来的ASCII字符
6.字符串型
作用:表示一串字符
两种风格
c风格字符串 —– char ch[] ={sadsa};
int main() { char ch[] = "asadfa"; cout<<ch<<endl; return 0; }
c++风格的字符串
string —变量名 = “字符串值”
#inclide <string> //用c++风格的字符串需要头文件 int main() { string str+ = "dasdafa"; }
7.布尔类型
作用:布尔类型代表真假的值
bool类型只有两个值:
true —真(本质1)
false —假(本质0)
※bool占1个字节大小
数据的输入–cin
作用:用于从键盘获取数据
关键字:cin
语法: cin>>变量
示例:
int main() { int a = 0; cout>>"请输入一个整型变量a赋值:"<<endl; cin>>a; cout>>"整型变量a"<<a<<endl; }
控制语句
1.分支语句
if-else
基本格式要求
注意:
if 下只有一条语句时, {} 可以省略, 但是建议初学者自己写代码时都加上花括号
else 只能和if搭配使用, 后面没有圆括号
if( 条件判断 ) //if(0 == n) 用到==, 建议将数字放在前面 { //执行语句; //执行语句2; //条件判断的结果为 真, 则执行花括号内的语句; }
if( 条件判断 ) { //条件判断的结果为 真, 则执行花括号内的语句; } else //只能和if搭配使用, 不能独立存在 { //if括号内"条件判断"的结果为 假, 则执行else后花括号内的语句; }
if( 条件判断1 ) //括号后没有分号 { //条件判断1的结果为 真, 则执行花括号内的语句; } else if( 条件判断2 ) { //条件判断2的结果为 真, 则执行花括号内的语句; } else if( 条件判断3 ) { //条件判断3的结果为 真, 则执行花括号内的语句; } else //后面不能由括号存在 { //前面n个if括号内"条件判断"的结果都为 假, 则执行else后花括号内的语句; }
示例:
#include <iostream> using namespace std; #include <string> int main() { int score; cin>>score; if(score > 600) { cout<<" 恭喜您考试一本大学"<<endl; }else if(score > 500){ cout<<"恭喜您考上二本大学"<<endl; }else if(score > 400){ cout<<"恭喜您考试三本大学"<<endl; } else{ cout<<"您没有考上本科大学"<<endl; } return 0; }
switch-case
基本格式要求
注意:
1.case 后面只能跟 字符常量 或 整形常量 或 字符串常量, 不能是浮点数,不能是表达式
'a' 字符常量 12 整型常量 0x3 整型常量 "hello" 字符串常量
2.恰当运用 break, 结束语句执行
3.如果语句中没有break, 会执行匹配结果向下所有的执行语句
4.每个 case 后 常量 的值必须各不相同,否则会出错
switch( 变量/表达式 ) { //常量表达式 一定是 字符常量 或 整形常量 或 字符串常量, 不能是浮点数,不能是表达式 // 'a' 字符常量 12 整型常量 0x3 整型常量 "hello" 字符串常量 case 常量: 执行语句; break; case 常量: 执行语句; break; case 常量: 执行语句; break; case 常量: 执行语句; break; case 常量: 执行语句; break; default: 执行语句; }
char n = 0; scanf("%c", &n); switch( n ) //结果一定是 字符常量 或 整形常量 或 字符串常量, 不能是浮点数 { case '@': 执行语句1; break; case '3': 执行语句2; break; case 56: 执行语句3; break; case 97: 执行语句4; break; case '&': 执行语句5; break; default: 执行语句6; break; }
示例:
#include <iostream> using namespace std; int main() { int score = 0; cout<<"您打的分数是:"<<endl; cin>>score; switch(score) { case 10: cout<<"今典电影"<<endl; break; case 9: cout<<"今典电影"<<endl; break; case 8: cout<<"今典电影"<<endl; break; case 7: cout<<"今典电影"<<endl; break; case 6: cout<<"一般电影"<<endl; break; case 5: cout<<"一般的电影"<<endl; break; default : cout<<"烂片"<<endl;break; } }
2.循环语句
while
作用:满足循环条件,执行循环语句
语法:while(循环条件){循环语句}
条件为真 执行语句
while( 判断语句 ) { //循环语句 //判断语句 结果 为真, 则一直执行 }
示例1:
#include <iostream> using namespace std; int main() { //while循环 int num =0; while(1) { if(0 <= num && num < 9) { num++; cout<<" "<< num; }else { break; } } }
示例2:
#include <iostream> using namespace std; #include <ctime> int main() { //时间种子 srand((unsigned int )time(NULL)); //系统生成一个随机数 int num = rand()%100+1; //生成一个0~99的随机数 //cout<<num<<endl; //玩家进行猜测 while(1){ int a =0; cin>>a; //判断玩家猜测 if( a > num ) { cout<<"猜测过大"<<endl; }else if(a < num){ cout<<"猜测过小"<<endl; }else { cout<<"猜对"<<endl; break; } } //猜对 退出游戏 //猜错 提示猜的结果 }
do while()
基础格式要求
注意:
最后要加分号
先执行一次,再进行判断
do{ //循环语句 //判断条件为真, 则进入循环 }while( 判断条件 );
示例
#include<iostream> using namespace std; int main() { int num = 0; do{ num++; cout<<" "<<num; }while(num < 10); return 0; }
示例:水仙花数
#include<iostream> using namespace std; int main() { //打印所有的三位数字 int num = 100; do{ int a=0; int b=0; int c=0; a = num%10; b = num/10%10; c = num/100; if(a*a*a+b*b*b+c*c*c ==num){ //如果是水仙花数打印 cout<<num<<endl; } num++; }while(num < 1000); }
for()循环
作用:满足循环条件,执行循环语句
语法:for(起始表达式;条件表达式;末尾表达式){循环语句}
注意:
()括号内 三条语句 用 分号 间隔开
初始语句1 只执行一次
()括号外没有分号
for( 初始语句1; 判断条件2; 每次循环后都会执行的语句3 ) { //循环体;4 //判断条件为真, 则进入循环5 } //语句6 1. 先执行 初始语句1 2. 执行 判断条件2, 结果为真, 执行循环体 4 5, 执行语句 3, 执行 判断条件2 3. 执行 判断条件2, 结果为假, 不进入循环, 执行循环后的语句6
示例—敲桌子
#include<iostream> using namespace std; int main() { int i = 0; int num; for(i = 1;i<=100;i++){ if(i%7==0 || i%10==7 || i/10 ==7){ cout<<"桥桌子"<<endl; }else{ cout<<i<<endl; } } return 0; }
练习 99乘法表
#include<iostream> using namespace std; int main() { int i,j; for(i = 1;i <=9;i++) { // cout<<i<<endl; for(j = 1;j <=i;j++) { cout<<j <<" * "<<i <<" = "<<j*i<<" "; } cout<<endl; } return 0; }
3.跳转语句
break()
用在 switch 语句中, 用于提前结束语句
用于循环内, 提前结束循环
continue()
只能作用在循环语句中, 用于结束本次循环, 提前进入下一次循环
goto()
无条件跳转语句
谨慎使用, 注意不要陷入死循环
会造成代码可读性差, 通常用于错误判断
数组
概述:
所谓的数组就是一个集合,里面存放着相同类型的数据元素
特点:
1.数组中的每个元素都是相同的数据类型 2.数组是由连续的内存位置组成的
一维数组
定义:
存储类型 数据类型 数组名[数组能存放的最大元素个数]
eg: int name1[100]; //存放100个int类型数据 char name2[1024]; //存放1024个char类型数据 double name3[1024]; //存放64个double类型数据数组名: 代表了数组这个整体, 数组名也就是数组的首地址常量
注意:
方括号内 一定是 常量, 数组的大小一定是固定的, 使用时一定不要超过界限 使用数组时超出 数组范围, 运行程序时可能会报段错误(已放弃 (核心已转储) / 系统崩 溃) 编译时 不会检查 段错误 段错误又称为 非法访问 内存 字符数组存放字符串时注意给'\0'留位置
初始化
1.完全初始化
给数组中的每一个元素勾 赋上初值
int num[10] = {0, 1, 22, 44, 66, 77, 43, 67, 98, 77};
2.部分初始化
按顺序给前面的元素赋值, 后面没有给是给初值的元素就默认为零
int num[10] = {0, 1, 22, 44, 66}; //num[5] - num[9] 的值为0 int num[10] = {0}; //数组内元素的初值都为零
3.缺省初始化
省略 方括号的 元素个数, 数组的最大元素个数 就是 花括号内的元素个数
不建议初学者使用
int num[] = {1,2,3,4,5,6,7,8,9}; //最多只能存放9个int类型变量 //int num[] = {0}; // 最多只能存放1个int类型变量 int n = 0; //语法角度没问题, 但是没有实际意义, 没有使用价值, 还会更麻烦, 还容易出错
练习:
一维数组的应用—–数组元素的逆序
#include<iostream> using namespace std; int main() { //实现数组元素的逆置 int arr[5] = {1,2,3,4,5}; for(int i = 0; i < 5;i++){ cout << " "<<arr[i]; } cout<<endl; int start = 0; int end = sizeof(arr)/sizeof(arr[0])-1; while(start < end){ int temp = arr[start]; arr[start] = arr[end]; arr[end] = temp; start++; end--; } cout <<"元素逆置后"<<endl; for(int i = 0; i < 5;i++){ cout << " "<<arr[i]; } }
冒泡排序:
#include<iostream> using namespace std; int main() { int arr[10] = {8,3,5,6,9,7,4,2,1,0}; for(int i=0;i<10;i++){ cout <<" "<<arr[i]; } cout<<endl; cout<< "排序后"<<endl; for(int i = 0;i<10;i++){ for(int j=0;j<9-i;j++){ if(arr[j] > arr[j+1]){ int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } for(int i=0;i<10;i++){ cout <<" "<<arr[i]; } return 0; }
二维数组
多个相同的一维数组的集合
定义:
存储类型 数据类型 二维数组名[一维数组的个数][一维数组中元素的个数]; 存储类型 数据类型 二维数组名[行][列]; eg: int num[3][6]; //由3个一维数组构成, 每个一维数组有6个元素, 总共有18个元素 char num[3][6]; //由3个一维数组构成, 每个一维数组有6个元素, 总共有18个元素 double num[3][6]; //由3个一维数组构成, 每个一维数组有6个元素, 总共有18个元素
示例
#include<iostream> using namespace std; int main() { int arr1[2][3] ={1,2,3,4,5,6}; for(int i=0;i<2;i++){ for(int j=0;j<3;j++){ cout <<arr1[i][j] <<" "; } cout <<endl; } }
二维数组的数组名
二维数组的名称用途:
1.可以查看内存空间大小
int arr[2][3] = { {1,2,3}, {4,5,6} }; cout << "二维数组占用内存空间为:"<<sizeof(arr)<<endl; cout << "二维数组第一行占用的内存为:"<<sizeof(arr[0])<<endl; cout <<"二维数组第一个元素占用的内存为"<<sizeof(arr[0][0])<<endl; cout <<"二维数组行数为:"<<sizeof(arr) / sizeof(arr[0])<<endl;
2.可以查看二维数组的首地址
cout <<"二维数组首地址为:"arr<<endl;
函数
概述:
作用:将一段经常使用的代码封装起来,减少重复代码,一个较大的程序,一般分为若干个程序块,每个模块实现特定的功能
函数的定义:
函数的定义一般主要有5个步骤:
1.返回值类型 2.函数名 3.参数表列 4.函数体语句 5.return 表达式
语法:
返回值类型 函数名 (参数列表) { 函数体语句 return 表达式 }
函数的调用
#include <iostream> using namespace std; int add(int &x,int &y); //形参 int add(int &x,int &y) { int z; z = x+y; return z; } int main() { int a =2; //实参 int b=3; int c =add(a,b); //调用函数 实参会传递给形式参数 cout <<c<<endl; return c; }
值传递
※所谓的值传递:就是函数调用时,实参将数值传给形参
※值传递时:如果形参发生变化,并不会影响实参
#include<iostream> using namespace std; void swap(int &a,int &b) { int temp; temp = a; a = b; b = temp; } int main() { int x=10; int y =20; swap(x,y); cout<< x<<y<<endl; }
函数的常见形式
常见的函数样式有4种
1.无参无返: 2.有参无参: 3.有参有返: 4.有参有返:
1.无参无返
#include<iostream> using namespace std; void test01()//注意 这里小括号里面不能写参数, //不然就不是所谓的“无参”了就是 //"有参无返“了; { cout<<"this is test01;"<<endl; return; } int main(){ //无参无返函数的调用; test01(); }
2.有参无返
#include<iostream> using namespace std; void test01()//注意 这里小括号里面不能写参数, //不然就不是所谓的“无参”了就是 //"有参无返“了; { cout<<"this is test01;"<<endl; return; } void test02(int a){ cout<<"this is test02 a="<<a<<endl; } int main(){ //无参无返函数的调用; test01(); //有参无返函数的调用; test02(100) ; }
2.无参有返
#include<iostream> using namespace std; void test01()//注意 这里小括号里面不能写参数, //不然就不是所谓的“无参”了就是 //"有参无返“了; { cout<<"this is test01;"<<endl; return; } void test02(int a){ cout<<"this is test02 a="<<a<<endl; } int test03()//无参有返,所以声明函数的时候就不能用 //viod了,就得用其他的函数类型;还有就是 //这是有”无参有返“所以 return 语句中就得 //写返回值了; { cout<<" this is test03 "<<endl; return 100;//别忘了写返回值 } int main(){ //无参无返函数的调用; test01(); //有参无返函数的调用; test02(100) ; //无参有返函数的调用: //因为它有返回值,所以我们需要定义一个空间来 //接收它的返回结果; int b= test03(); cout<<"b="<<b<<endl; }
4.有参有返
#include<iostream> using namespace std; void test01()//注意 这里小括号里面不能写参数, //不然就不是所谓的“无参”了就是 //"有参无返“了; { cout<<"this is test01;"<<endl; return; } void test02(int a){ cout<<"this is test02 a="<<a<<endl; } int test03()//无参有返,所以声明函数的时候就不能用 //viod了,就得用其他的函数类型;还有就是 //这是有”无参有返“所以 return 语句中就得 //写返回值了; { cout<<" this is test03 "<<endl; return 0;//别忘了写返回值 } int test04(int a){ cout<<"this is test04 a="<<a<<endl; return 50; } int main(){ //无参无返函数的调用; test01(); //有参无返函数的调用; test02(100) ; //无参有返函数的调用: //因为它有返回值,所以我们需要定义一个空间来 //接收它的返回结果; int b= test03(); cout<<"b="<<b<<endl; int c=test04(100); cout<<"c="<<c<<endl; }
函数声明
作用:告诉编译器函数名称如何调用函数,函数的实际主体可以单独定义
指针
指针的定义及使用
指针的作用:通过指针来访问内存
内存编号是从0开始记录的,一般用十六进制表示
可以利用指针变量保存地值
※ 指针就是一个地址
#include<iosteram> using namespace std; int main(){ int a =10; //指针定义语法:数据类型 * 指针变量名; int *p; //让指针p记录a的地址 p = &a; cout<<"a的地址为:"<<&a<<endl; cout<<"指针p为"<<p<<endl; //2.使用指针 //可以通过解引用的方式来找到指针指向的内存 //指针前加 * 代表解引用,找到指针指向内存的数据 *p = 1000;
指针所占的内存空间
1. 不同数据类型占用的内存空间
2. 指针占用的内存空间
空指针和野指针
空指针:指针变量指向内存中编号为0的空间
用途:初始化指针变量
注意:空指针指向的内存是不可以访问的
c++的编译过程
1.预处理 g++ -E text.c -o text.i 2.编译 g++ -S text.c -o text.s 3.汇编 g++ -c text.c -o text.o 4.链接 g++ text.o -o text
c 和 c++的兼容和差异
1.创建文件和编译
创建文件:vim test.cpp
编译: g++ test.cpp
运行程序: ./a.out
2.强制类型转换
c
int a = 2; double b = 3; a = (int)b;
c++
int a = 2; double b = 3; a = int (b);
3.引用
相当于给变量取别名,引用变量和原本变量公用一个地址空间,操作引用变量相当于标量本身
基本用法
eg: int a = 2; int &b = a; //定义了一个引用变量b,相当于给变量a取一个别名b int d = 22; b = 12; //a == 12 因为a和b共用同一片地址空间,修改b的值也相当于在修改a int &c = a; //right int &c = d; //error 重复引用,有歧义
引用和指针的区别:
1.引用必须初始化某个变量的别名,而指针可以为空 2.修改引用时候修改的是引用所代表的原变量的值,而修改指针时候则是修改指针所指向的地址
作用:
※函数传参,形参可以改变实参
※引用作为返回值的作用
注意:
※多个变量不能使用相同的引用,但是多个引用可以使用相同的变量
※函数不能返回局部变量的引用
4.内联函数
内联函数声明的一般形式:
inline 存储类型 数据类型 函数名(形式参数) { 函数的功能模块 }
内联函数的作用就是为了提高程序运行效率
程序运行时,将内联函数中的源码替换函数执行(拷贝)从而提高效率
内敛的函数的行数最好不要超过5行,最好不要有其他控制语句(for while、switch)
5.函数重载
c++中允许定义函数名相同,参数不同的函数来构成函数重载
让函数名相同,提高复用性
函数重载的条件:
1、函数名相同 2、参数不同(个数、类型、顺序) 3.函数在同一个作用域下 ※返回值对函数重载没有影响 ※返回值不能作为函数重载的条件
eg: int add(int x, int y); // a int add(char x, char y); //b char add(int x,int y); //c int add(int x, int y, iny z); // d //函数a和函数b是重载关系 //函数a和函数c是相同的两个函数,重复定义 //函数a和函数d是重载关系 //函数c和函数d是重载关系 int add(int x, double y); //e int add(double x, int y); //f //函数e和函数f也是重载关系 add(2, 3); //调用函数a add('a', 'b'); //调用函数b add(2, 3, 4); //调用函数d
※调用函数时,系统会根据用户传参选择调用指定函数
函数重载的注意事项
1.引用作为函数重载的条件
6.默认函数–函数形参
※C++中允许在函数定义时,给形参赋初始值,定义之后在函数传参时,可以不传入默认参数
※如果不传默认参数,则在调用函数时,使用形参的初值参与各种运算
int add(int x, int y, int z = 0); //参数z使用默认参数,默认值为0 add(1, 2, 3) == 6 //给默认参数传参,将3的值替换默认参数值0 add(1, 2) == 3 //没有给默认参数传参,则z的值使用默认参数值0 int add(int x, int y = 0, int z); //error y之后的所有参数必须设置为默认参数 add(1, 2)// 2不清楚是给y赋值还是给z赋值
注意:
1、如果默认参数设置了多个,那么参数赋值时应该从左到右依次赋值
2、如果函数参数中设置了默认参数,那么该参数之后的所有参数必须设置为默认参数
7.名字空间
名字空间声明的一般形式:
namespace 名字空间名
{
变量和函数;
}
使用名字空间的方法:
1、using namespace 名字空间名; //从该语句开始,名字空间中的所有数据都能访问
eg:
using namespace std; //使用名字空间std
2、名字空间名::变量名或者函数名 // 指定访问该名字空间中的数据
eg:
std::cout << "hello" << std::endl;
8.连接性和存储性
连接性:描述了名称如何在各个单元中共享
外部链接:名称可以在文件间共享 (全局变量)
内部链接:名称仅仅可以在一个文件中的函数共享
变量说明限定符
auto 、 register 、 static 、 extern、 thread_local、mutable、const、volatile
9.堆区动态内存开辟
c语言:
int *p = (int *)malloc(sizeof(int)); free(p); p = NULL;
c++
int *p = new int; //在堆区开辟int类型大小的空间赋值给指针变量p delete p; //释放指针p的地址 p = NULL; 数组堆区空间开辟: int a[3]; int *p =new int[3]; //在堆区开辟5个int类型的数据类型大小的空间,首地址赋值给p delete []p; //释放整个数组空间
示例:
#incluude<iostrean> using namespace std; struct stu{ int age; } int main() { int a = 2; int *p = new int; *p = a; cout <<p<<endl; int * p =new struct stu; cout <<*p<<endl; delete p;; p = NULL; return 0; }
10.结构体
C++中结构体既可以定义变量,也可以定义函数
C++中结构体所有成员变量和成员函数都可以设置访问权限
访问权限:
public:公共权限,结构体内外都可以访问的数据
protected:保护权限,结构体内可以访问,结构体外不能直接访问,结构体外可以通
过结构体内的共同函数接口访问
private:私有权限,结构体内可以访问,结构体外不能直接访问,结构体外可以通过
结构体内的共同函数接口访问
protected和private权限的区别,在类的继承中才能看到
11.this 指针
this指针的用途:
※当形参和成员变量同名时,可以用this来区分
※在类的非静态成员函数中返回对象本身,可以用 return *this
//解决名称冲突 #include<iostream> using namespace std; class person { public: person(int age) { this-> age =age; } int age; }; void test01() { person p1(18); cout <<"p1的年龄为:"<<p1.age <<endl; } int main() { test01(); }
12.new操作符
利用new操作符在堆区开辟空间
堆区开辟数据需要程序员手动释放,释放利用操作符 delete
语法:
new [数据类型]
利用new创建的数据,会返回该数据对应的指针
类和对象
1.类和对象的关系
1.类是对象的抽象 2.对象是类的实例化
2.类的定义
类是用户自定义的一种
数据类型
class 类名 { public: //公共权限,类内和类外都能直接访问 成员变量或者成员函数 ...... protected: //保护权限,类内可以直接访问,类外不能直接访问,但是可以通过调用类中的公共成员函数 间接访问 成员变量和成员函数 ....... private: //私有权限,类内可以直接访问,类外不能直接访问,但是可以通过调用类中的公共成员函数 间接访问 成员变量和成员函数 ....... };
示例:
#include<iostream> using namespace std; class stu { public: void go_to_school() { cout << "go to school" <<endl; } void set_age(int m_age) { age = m_age; } int get_age() { return age; } private: int age; }; int main() { stu zhangsan; //zhangsan.age =15; zhangsan.set_age(15); cout << zhangsan.get_age() <<endl; return 0; }
3.对象的定义
创建的一般形式:
类名 对象名;
通过对象访问成员变量或者成员函数:
对象名.成员名;
4.构造和析构函数
1.构造函数
1.构造函数,没有返回值也不写void 2.函数名称与类名相同 3.构造函数可以有参数,因此可以发生重载 4.程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
#include <iostream> using namespace std; class Person{ //1.构造函数 //没有返回值 //构造函数可以有参数,也可以没有参数 //创建对象的时候,函数是可以自动调用的,而且只调一次 public: Person(){ cout << "person构造函数调用:" <<endl; } //2.析构函数 进行清理的操作 //没有返回值 ,不写 Void //函数名和类名相同,在名称前加~ //析构函数不可以有参数的,不可以发生重载 //对象在销毁前 会自动调用析构函数,而且只会调一次 ~Person(){ cout << "person 的析构函数调用" <<endl; } }; void test(){ Person p; //在栈上的数据,执行完毕之后释放这个对象 } int main(){ Person p; test(); }
(1)构造函数:一般用于成员做初始化
构造函数的一般形式: 构造函数名(参数列表)//构造函数名必须和类名相同 { 代码模块: }
class Demo { public: Demo(int val) // Demo类的构造函数,在创建对象时,由系统自动调用 { value = val; cout << __func__ << ":" << __LINE__ << endl; //打印函数名和行号 } private: int value; }; Demo obj(10); //定义了一个obj对象,并用10给val传参,用于给value值做初始化,对象后面的参数 就是构造函数的参数
注意:
在创建对象时,用户定义的构造函数会由系统自动调用
如果在类中没有定义构造函数,会由系统自动创建一个并且调用(函数中是空实现)
2.构造函数的分类
有参构造、无参构造、拷贝构造
#include <iostream> using namespace std; class Person{ public: Person(){ //默认构造 cout << "Person的无参构造函数调用" <<endl; } Person(int a){ age = a; cout << "Person的有构造函数调用" <<endl; } //拷贝构造 Person(const Person &p){ //拷贝的同时按照引用的方式传进 age = p.age; cout << "Person拷贝构造函数调用" <<endl; } ~Person(){ cout << "Person的析构函数调用" <<endl; } int age; }; void test01(){ //1.括号法 // Person p1;//无参构造函数 // Person p2(10); //有参构造函数 // Person p3(p2); //拷贝构造函数 //注意事项 //调用默认构造函数的时候,不要加括号()Person p1() //因为编译器觉得这个会是一个声明不会觉得是在创建对象 // //2.显示法 // Person p1; // Person p2 = Person(10); // person p3 = Person(&p2); //拷贝构造 // cout << "p2的年龄是:"<<p2.age<<endl; //注意事项: //不要利用拷贝构造函数 初始化匿名对象 // //3.隐式转换法 Person p4 =10; //相当于写了 Person p4 = Person(10); } int main(){ test01(); }
3.拷贝构造的调用时机
#include <iostream> using namespace std; //拷贝构造的调用时机 //1.使用一个已经创建完毕的对象来初始化一个新对象 //2.值传递的方式给函数参数传值 //3.值方式返回局部对象 // class Person{ person(){ cout << "Person的默认构造调用" <<endl; } Person(int age){ m_Age = age; } Person(const Person &p){ cout << "Person默认的拷贝构造函数的调用" <<endl; m_Age = p.age; } ~Person() { cout <<"Person的析构函数的调用" <<endl; } private: int m_Age; }; void test01(){ } int main() { test01(); }
5.构造函数的调用规则
#include <iostream> using namespace std; //构造函数的调用规则 //1.创建一个类,C++编译器会给每个类都添加至少3个函数 //默认构造(空实现) //析构函数(空实现) //拷贝构造(值拷贝) // //2.如果我们写了有参构造函数,编译器就不再提供默认构造,但依旧提供拷贝构造 //3.如果我们提供了拷贝构造函数,编译器就不会提供其他普通构造函数 class Person { public: Person() { cout << "Person的默认构造函数:"<< endl; } Person(int age) { cout<<"Person的有参构造函数调用"<<endl; m_Age = age; } Person(const Person &p){ cout<<"Person的拷贝构造函数调用" <<endl; } ~Person() { cout<< "Person析构函数的调用" <<endl; } int m_Age; }; void test(){ Person p; p.m_Age = 18; Person p2(p); cout<< "p2的年龄为:" <<p2.m_Age<<endl; } void text02() { int main() { test(); }
6.深拷贝和浅拷贝
7.初始化列表
用来初始化属性
#include <iostream> using namespace std; //初始化列表 // class Person { public: //初始化参数列表 Person(int a,int b,int c):m_A(a),m_B(b),m_C(c) { //m_A = a; //m_B = b; //m_C = c; } int m_A; int m_B; int m_C; }; void text01() { Person p(10,50,60); cout << "m_A:"<< p.m_A <<endl; cout << "m_B:"<< p.m_B <<endl; cout << "m_C:"<< p.m_C <<endl; } int main() { text01(); }
8.类对象作为类成员
#include <iostream> using namespace std; #include <string> //类对象作为类成员 // // class Phone { public: Phone(string PName) { cout<<"Phone的构造函数调用"<<endl; m_PName = PName; } ~Phone() { cout << "Phone析构函数调用" <<endl; } string m_PName; }; class Person { public: Person(string name,string pName):m_Name(name),m_Phone(pName) { cout << "Person构造函数的调用" << endl; } //姓名 string m_Name; //手机 Phone m_Phone; ~Person() { cout <<"Person析构函数调用" <<endl; } }; //当其他类对象作为本类成员,构造时候先构造对象,再构造自身 //析构的顺序和构造相反 void test01() { Person p("利斯","华为"); cout << p.m_Name << "拿着" << p.m_Phone.m_PName<<endl; } int main() { test01(); } ******************* Phone的构造函数调用 Person构造函数的调用 利斯拿着华为 Person析构函数调用 Phone析构函数调用
9.静态成员
静态成员就是在静态成员变量和成员函数前面加 static 称为静态成员
分类:静态成员函数、静态成员变量
#include <iostream> using namespace std; //静态成员变量 // class Person { public: //所有对象都共享一份数据 //在编译阶段就分配了内存 //类的声明,类外初始化操作 static int m_A; }; int Person::m_A = 10; int test01() { Person p; cout << p.m_A <<endl; } void test02() { //静态成员变量 不属于某个对象上,所有对象贡享一份数据 //因此静态成员变量有两种访问方式 // //1.通过对象来访问 // //2.通过类名进行访问 int main() { test01(); } ************************************************ 静态成员函数 #include <iostream> using namespace std; //静态成员函数 //所有对象共享同一个 class Person { public: //静态成员函数 static void func() { cout << "static void fun调用" <<endl; } ~Person() { cout << "Person析构调用"<<endl; } }; //1.通过对象来访问 //2.通过类名来访问 void test01() { //通过对象来访问 Person p; p.func(); //通过类名来访问 Person::func(); } int main() { test01(); }
10.成员变量和成员函数分开存储
#include <iostream> using namespace std; //成员变量 和 成员函数 分开存储的 class Person { int m_A; //非静态成员变量 属于类的对象上 static int m_B; //静态成员变量 不属于类对象上 void funv(); //非静态成员函数 不属于类对象上 static void funb(); //静态成员函数 不属于类的对象上 }; void test01() { Person p; //空对象占用内存空间为 1 //C++编译器会给每个空对象也分配一个字节空间,为了区分空对象占内存的位置 //每个空对象都应该有自己独一无二的地址 cout << "size of p " << sizeof(p) <<endl; } int main() { test01(); }
11.空指针访问成员函数
#include <iostream> using namespace std; //空指针调用成员函数 class Person { public: void showClassName() { cout << "this is Person class " <<endl; } void showPerrAge() {//保存的原因是因为传入的空指针 cout << "age = " <<this-> m_Age <<endl; } int m_Age; }; void test() { Person *p = NULL; p->showClassName(); p->showPerrAge(); } int main() { test(); }
5.const
1.const修饰成员变量
1.const修饰的变量变成只读,不能修改它的值 2.const成员变量以参数化列表的形式完成赋值操作
2.const修饰的成员函数
数据类型 函数名(参数列表)const 1.const成员函数中,所有的成员变量的值都不能被修改 2.const修饰的成员函数内部不能调用非const修饰的成员函数 3.非const修饰的成员函数可以调用const修饰的成员函数在
3.const修饰对象
const---常对象 const 类名.对象名; 类名 const 对象名; 常对象不能调用非const成员函数,常对象中,所有成员边梁的值都不能被修改
#include <iostream> using namespace std; class demo{ public: demo(int i =0):i(i) { } int get_val()const //函数内部不能出现改变的逻辑 { return i; } private: const int i; }; int main() { demo A; cout << A.get_val() <<endl; }
6.static
修饰全局变量
全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式,static全局变量只初使化一次,防止在其他文件单元中被引用;
修饰局部变量
把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。static局部变量只被初始化一次,下一次依据上一次结果值;
修饰函数
static修饰的函数叫做静态函数
static修饰的成员变量
static修饰的成员变量需要再类外部进行初始化才能使用static 修饰的变量不属于类,没有对象生成也存在
static 修饰的变量和对象无关,不管你有多少个对象或者是没有对象,我都只有一份
static 修饰的变量可以被非static修饰的函数访问
static修饰成员函数
static修饰的成员函数不能访问非static修饰的成员变量
static修饰的函数不能访问非static修饰的函数
非static修饰的函数可以访问static修饰的函数
练习:记录对象的实时个数
要求:static变量权限是私有的
至少有一个static修饰的接口取获取这个变量
构造函数写两个,一个普通构造,一个拷贝构造
#include <iostream> using namespace std; class demo{ public: demo() { number++; } demo(const demo&obj) { number++; } ~demo() { number--; } static int get_val() { return number; } private: static int number; }; int demo::number = 0; void func() { demo A,B; demo C = B; cout<<demo::get_val()<<endl; } int main() { demo A; func(); cout<<A.get_val()<<endl; }
7.友元函数
作用:频繁读写类的数据时提高效率
1.全局函数作为类的友元函数,可以访问类的私有成员
#include <iostream> using namespace std; #include <string> class Building { //全局函数访问类的私有成员,需要用到友元函数 friend void goodGay(Building *building); public: Building() { m_SittingRoom = "客厅"; m_BedRoom = "卧室"; } public: string m_SittingRoom; //客厅 private: string m_BedRoom; //卧室 }; //全局函数 void goodGay(Building *building) { cout << "好基友全局函数 正在访问:" <<building->m_SittingRoom<<endl; cout << "好基友全局函数 正在访问:" <<building->m_BedRoom<<endl; } void test() { Building building; goodGay(&building); } int main() { test(); }
2.成员函数作为一个类的友元函数
3.类作为另外一个类的友元类
#include <iostream> using namespace std; #include <string> class Building; class GoodGay; class GoodGay { public: void visit(); //参观函数 访问Building中的属性 Building *building; ~GoodGay() { } }; class Building { friend class GoodGay; //友元 public: Building(); public: string m_SittingRoom; private: string m_BedRoom; }; //类外去写成员函数 Building::Building() { m_SittingRoom = "客厅"; m_BedRoom = "卧室"; } GoodGay::GoodGay(){ //创建一个建筑物的对象 building = new Building; } void GoodGay::visit() { cout << "好基友类正在访问:" << building->m_SittingRoom<<endl; cout << "好基友类正在访问:" << building->m_BedRoom<<endl; } void test() { GoodGay gg; gg.visit(); } int main() { test(); } 注意:友元是单向传递的,没有继承关系
8.运算符的重载
为什么使用运算符重载
因为运算符默认支持基本数据类型,但是自定义也需要,所以重载,可以让代码简洁,编写更迅速
没有办法被重载的运算符
总结:
1.对于内置的数据类型的表达式的运算符是不可能发生改变的
2.不要滥用运算符重载
8.1 “ + ”运算符
8.1.1成员函数重载的 + 符
#include<iostream> using namespace std; class person { public: //成员函数来重载的加号运算符 person operator+(person &p) { person temp; temp.m_A = this->m_A+p.m_A; temp.m_B = this->m_B+p.m_B; return temp; } int m_A; int m_B; }; void test01() { person p1; p1.m_A=10; p1.m_B=10; person p2; p2.m_A=10; p2.m_B=10; person p3 = p1+p2; cout << "p3.m_A" << p3.m_A<<endl; cout << "p3.m_B" << p3.m_B<<endl; } int main() { test01(); return 0; }
8.1.2全局函数来重载 +
lass person 11 { 12 public: 13 14 int m_A; 15 int m_B; 16 17 person operator+ (person &p1,person &p2) 18 { 19 person temp; 20 temp.m_A = p1.m_A + p2.m_A; 21 temp.m_B = p1.m_B + P2.m_B; 22 return temp; 23 } 24 }; 25 26 void test01() 27 { 28 person p1; 29 p1.m_A=10; 30 p1.m_B=10; 31 person p2; 32 p2.m_A=10; 33 p2.m_B=10; 34 35 person p3 = p1.operator+ (p2); 36 cout << "p3.m_A" << p3.m_A<<endl; 37 cout << "p3.m_B" << p3.m_B<<endl; 38 39 }
8.2 “<<”运算符重载
8.3递增运算符重载
作用:通过重载递增运算符,实现自己的数据类型
前置递增返回的是引用 return *this
后置递增返回的是值
#include<iostream> using namespace std; class zizeng { friend ostream& operator<<(ostream& cout,zizeng myint); public: zizeng() { m_A = 0; } //重载前置运算符 zizeng& operator++()//返回引用为了一直对一个数据进行递增的操作 { m_A++; //将自身进行返回 return *this; } //后之递增 zizeng operator++(int)//int 代表的是占为参数 可以区分前后置 { zizeng t =*this; m_A++; return t; } private: int m_A; }; //重载<<运算符 ostream& operator<<(ostream& cout,zizeng myint) { cout <<++myint.m_A; return cout; } void test01() { zizeng a; cout<<a<<endl; cout<<++a<<endl; } void test02() { zizeng b; cout <<b<<endl; cout <<b++<<endl; } int main() { test01(); test02(); }
※经典示例
#include <iostream> using namespace std; const int N = 10; class Arr{ public: Arr(int size) // { number++; addr = new char[size]; len = 0; this->size = size; } Arr(char*p) { number++; //数参数字符串的长度 int count = 0; char*h = p; while(*h++) count++; //创建堆区空间 addr = new char[count+N]; char*q = addr; //赋值堆区内容 while(*p) { *q++ = *p++; } *q = *p; len = count; size = count+N; } Arr(const Arr& obj) { number++; len = obj.len; size = obj.size; addr = new char[size]; char*p = obj.addr; char *q = addr; while(*p) { *q++ = *p++; } *q = *p; } Arr(Arr &&obj) { number++; len = obj.len; size = obj.size; addr = obj.addr; obj.addr = nullptr; } ~Arr() { number--; if(addr != nullptr) delete []addr; } int get_len()const { return len; } char* get_data()const { return addr; } void reset_addr(const int size) { char buf[len+1]; char *p = addr; char *q = buf; while(*p) { *q++ = *p++; } *q = *p; delete []addr; addr = new char[size]; p = addr; q = buf; while(*q) { *p++ = *q++; } *p = *q; } char *set_data(const char*p) { int count = 0; const char *h = p; while(*h++) count++; len = count+len; if(count >= size) { //扩容 reset_addr(count + N); } char*q = addr; while(*p) { *q++ = *p++; } *q = *p; return addr; } char* cat(const char *p) { const char *h = p; int count = 0; while(*h++) count++; len = count+len; if(count+len+1 > size) { reset_addr(count+len+N); } cout<<"addr="<<addr<<endl; char *q = addr; while(*q++); q--; while(*p) { *q++ = *p++; } *q = *p; return addr; } friend char&get_val(const int pos,Arr &obj); static int get_number() { return number; } private: static int number; char *addr; int len; //数组的长度 int size; //空间长度,装数据 }; int Arr::number = 0; char &get_val(const int pos, Arr &obj) { /* if(pos > obj.len) return '&'; //抛出异常 */ return obj.addr[pos]; //*(addr+pos) } int main() { char buf[10] = "hello"; Arr A(100); Arr B(buf); Arr C = B; Arr D = buf; D.cat(buf); //A = D+C; //D += buf; //D = "pppp"; D.set_data("aaaaaaaaaaaaaaaaaa1111111111111111111122222222222222222233333333333333333"); cout<<D.get_data()<<endl; cout<<D.get_len()<<endl; get_val(5,D) = '1'; cout<<get_val(5,D)<<endl; cout<<D.get_data()<<endl; cout<<Arr::get_number()<<endl; }
2. #include <iostream> //#include <string.h> #include <cstring> using namespace std; const int N = 10; class String{ public: explicit String(const char*p) { len = strlen(p); addr = new char[len+N]; size = len+N; strcpy(addr, p); cout<<"nomal"<<endl; } /* String(String&&obj) { len = obj.len; size = obj.size; addr = obj.addr; obj.addr = nullptr; } */ String& operator+=(const char*p) { int len = strlen(p); if(size <= len+this->len) { char buf[this->len+1]; strcpy(buf,addr); delete []addr; addr = new char[this->len+len+N]; strcpy(addr, buf); size = len+this->len+N; } this->len = len+this->len; strcat(addr,p); return *this; } String& operator=(const char*p) { cout<<"1"<<endl; int len = strlen(p); if(len >= size) { char buf[this->len+1]; strcpy(buf,addr); delete []addr; addr = new char[len+N]; strcpy(addr, buf); size = len+N; } this->len = len; strcpy(addr, p); return *this; } String& operator-=(const String&obj) { char*p = strstr(addr, obj.addr); if(NULL == p) { return *this; } else { char*q = p+obj.len; do { q = p+obj.len; *p = *q; p++; }while(*q); len = len-obj.len; return *this; } } String operator+(const String &obj)const { char buf[len+obj.len+1]; strcpy(buf, addr); strcat(buf, obj.addr); String A(buf); return A; } friend ostream& operator<<(ostream &os, String &obj); // friend istream& operator>>(istream &is, String &obj); private: char *addr; int len; int size; }; ostream& operator<<(ostream &os, String &obj) { os<<"addr="<<obj.addr<<" len="<<obj.len<<" size="<<obj.size; return os; } /* friend istream& operator>>(istream &is, String &obj) { int len; cout<<"please input len "<<endl; cin>>len; cout<<"please input addr"<<endl; char buf1[len+1]; cin>>buf1; cout<<"please input size"<<endl; int size; cin>>size; if(size > obj.size) 如果size大于就要重新开辟空间 if(len > obj.size) { delete []addr; addr = new char[len+N]; obj.size = len+N; } obj.len = len; strcpy(obj.addr,buf1); return is; } */ int main() { /* String A("hello"); A += "yyy"; */ String B = "yyy"; /* A -= B; cout<<A<<endl; // cin>>B; String C = A+B; cout<<C<<endl; */ }
9.模板
9.1类模板
使用模板的目的就是让程序员编程与类型无关的代码
模板是泛型编程的基础
函数模板:
函数模板针对仅参数类型不同的函数
类模板:
类模板针对仅数据成员和成员函数类型不同的类
1.语法:
template<typename T> 类
2.类模板和函数参数的区别
1.类模板没有自动推导的方式 2.类模板在模板参数列表中可以默认参数
3.类模板中成员函数的创建时机
1.普通函数类中的成员函数一开始就可以创建 2.类模板中的成员函数在调用时才能创建
示例:
#include<iostream> using namespace std; class Person { public: void showPerson() { cout << "Person show"<<endl; } }; class Person2 { public: void showPerson2() { cout << "Person2 show"<<endl; } }; template<class T> class MyClass { public: T obj; //类模板中的成员函数 void func1() { obj.showPerson(); } void func2() { obj.showPerson(); } }; void test01() { MyClass<Person>m; m.func1(); m.func2(); } int main() { test01(); }
4.类模板与继承
当类模板碰到继承时候,需要注意以下几点
1.当子类继承父类,是一个类模板时候,子类在声明的时候,要指定出父类中T的类型 2.如果不值得,编译器无法给子类分配内存 3.如果想灵活指出父类中T的类型,子类也需要变成类模板
示例:
#include<iostream> using namespace std; template<class T> class Base { T m; }; class son : public Base<int> { }; template<class T1,class T2> class son2 : public Base<T2> { public: son2() { cout << "T1的类型为:" << typeid(T1).name() <<endl; cout <<"T2的类型为:" <<typeid(T2).name() <<endl; } T1 obj; }; void test01() { son s; } void test02() { son2 <int, char > s2; } int main() { test02(); }
9.2函数模板
c++另一种编程思维 泛型编程 主要就是就是模板 c++ 提供两种模板机制:函数模板和类模板
函数模板的作用:
建立一个通用函数,其函数的返回值类型和形参类型可以不具体指定 用一个虚拟的类型来代表
语法:
template<typename T> 函数声明或定义
总结:
1.函数模板利用关键字 temoplate 2.使用函数模板有两种方式:自动类型推导、显示指定类型 3.模板的目的:提高复用性、将类型参数化
函数模板的注意事项:
1.自动类型推导,必须推导出一致的数据类型T,才可以使用 2.模板必须要确定出T的数据类型,才可以使用
普通函数和函数模板的调用规则
1.如果函数模板和普通模板都可以实现,优先调用普通模板 2.可以通过空模板参数列表来强制调用函数模板 3. 函数模板可以发生重载 4.如果函数模板可以产生更好的匹配,优先调用函数模板
模板的局限性
模板不是万能的,有些特定的数据类型,需要用具体化方式来实现
示例
#include <iostream> using namespace std; template<class T> //声明一个模板,告诉编译器代码中紧跟着的T不会报错,T是通用的数据类型 void func(T arr[],T len) { int temp; for(int i=0;i<len;i++){ for(int j=0;j<len-1;j++){ if(arr[j]>arr[j+1]){ temp=arr[j]; arr[j]=arr[j+1]; arr[j+1]=temp; } } } } int main() { int arr[10] ={9,8,7,6,5,4,3,2,1,0}; func(arr,10); int i; for(i=0;i<10;i++){ cout <<" "<<arr[i]; } }
类模板对象做函数参数
一共有三种传入方式: 1.指定传入类型 ---直接显示对象的数据类型 2.参数模板化 --- 将对象中的参数变为模板进行传递 3.整个类模板化 --- 将这个对象类型 模板化进行传递
10.继承
面向编程是一套将现实事物抽象化的方法论,而实现事务相互之间都有充满联系的
有些类与类之间存在特殊的关系,如图:
10.1继承的好处:
减少代码的重复 增大编程效率
语法:
class 子类 : public 父类
子类:也称派生类 父类 :也称基类
示例:继承
#include<iostream> using namespace std; //继承 //普通实现页面 class base { public: void hearder() { cout << "首页、公开课、登陆、注册、,,,(公共头部)" <<endl; } void footer() { cout << "帮助中心、交流合作、站内地图" <<endl; } void left() { cout << "java、python、c++、。。。(公共分类列表)" <<endl; } void contene() { cout << "java学科视频" <<endl; } }; class python : public base { public: void contene() //继承基类没有的属性 { cout << "python学科视频"<<endl; } }; class java : public base { public: void contene() { cout <<"java学科视频" <<endl; } }; void test01() { python p; p.hearder(); p.footer(); p.left(); p.contene(); } void test02() { java ja; ja.hearder(); ja.footer(); ja.left(); ja.contene(); } int main() { test01(); cout <<"----------------"<<endl; test02(); }
10.1继承方式
一共有三种
共有继承 保护继承 私有继承
1.父类私有的,子类都无访问权限 2.保护继承,父类中的公共和保护权限继承到子类中都成保护权限 3.私有继承,父类中的公共和保护权限继承到子类中都成私有权限
10.2继承中的对象模型
父类中所有非静态成员属性都会被子类继承下去 父类中的私有成员属性,是被编译器隐藏了,因此访问不到,但是确实继承下去了
示例:
#include<iostream> using namespace std; class father { public: int m_A; protected: int m_B; private: int m_c; }; class son1 :public father { public: int m_D; }; void test01() { cout << "size of son1"<< " " <<sizeof(son1) <<endl; } int main() { test01(); }
10.3继承中构造和析构顺序
1.继承中的构造和析构顺序如下: 先构造父类,再构造子类,析构顺序与构造的顺序相反
#include<iostream> using namespace std; class base { public: base() { cout << "f构造" <<endl; } ~base() { cout << "f析构" <<endl; } }; class son : public base { public: son() { cout <<"son构造"<<endl; } ~son() { cout << "son析构" <<endl; } }; void test01() { base b; cout <<" -------------------"<<endl; son s; } int main() { test01(); return 0; }
10.4继承同名成员处理方式
访问子类同名成员 直接访问即可 访问父类同名成员 需要加作用域
1.如果通过子类对象 访问到父类中的同名成员,需要添加作用域
2.如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有的同名成员函数
示例:
#include <iostream> using namespace std; //继承中同名函数的处理 class base { public: base() { m_A = 100; } void func() { cout << "base -fun"<<endl; } int m_A; }; class son :public base { public: son() { m_A =200; } void func() { cout <<"son-func"<<endl; } int m_A; }; //同名的成员属性处理 void test01() { son s; cout << "son 下m_A = "<< s.m_A<<endl; //如果通过子类对象 访问到父类中的同名成员,需要添加作用域 cout << "base 下m_A=" <<s.base::m_A <<endl; } //同名的函数处理 void test02() { son s; s.func(); //直接调用,调用子类中的同名成员 //调用父类的 s.base::func(); //如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有的同名成员函数 //如果访问到父类中被隐藏的同名成员函数 需要加作用域 s.base::func(100); } int main() { test01(); test02(); }
10.5继承同名静态成员的处理方式
※静态成员和非静态成员出现同名,处理方式一致
1.访问子类同名成员 直接访问即可 2.访问父类同名成员 需要加作用域
#include<iostream> using namespace std; class base { public: static int m_A; static void func() { cout <<"base-func"<<endl; } }; int base::m_A =100; class son : public base { public: static int m_A; static void func() //静态函数 { cout <<"son-func"<<endl; } }; int son::m_A=200; //同名的静态的成员属性 void test01() { son s; base b; cout <<s.m_A<<endl; cout <<s.base::m_A<<endl; //通过类名的方式访问 cout <<base::base::m_A<<endl; } //通过静态成员函数 void test02() { //通过对象 son s; s.func(); s.base::func(); //通过类名访问 son::func(); base::func(); } int main() { test01(); test02(); }
10.6 多继承语法
11.多态
覆盖/重写
作用域不同、函数名相同、参数相同,返回值不同,要有virtual
重载
作用域相同、函数名相同,参数不同
隐藏/重写
作用域不同,函数名相同,参数不同
什么叫做多态?
多态就是统一接口实现多种方法,它是运行时确认调用的接口(动态)、他是通过覆盖实现
覆盖是:作用域不同,函数名相同,参数相同,返回值相同,要有virtual
多态的实现底层依赖与虚函数
虚函数表
存在虚函数的类中,类的前4/8个字节存储是虚函数的首地址
纯虚函数
存在虚函数的类中,类的前4/8个字节存储的是虚函数的首地址
纯虚函数
格式:virtual 返回值 函数名 (参数) =0;
拥有纯虚函数的类叫做抽象类
抽象类不能创建实例/对象
纯虚函数需要重写
析构函数最好是设计成虚函数
多重继承可能引发使用基类成员函数出现二义性
将相同的基类作为虚函数,用虚继承、这样相同的数据和函数就只有一个拷贝和映射
限制构造
如果构造函数是保护权限,可以用继承和友元函数的方法去构造
如果
构造函数是私有权限,就只能使用友元函数的方法去构造
动态多态满足条件:
1.有继承关系
2.子类重写父类的虚函数
动态多态的使用
父类的指针或引用 执行子类的对象
1.多态的实例:
#include <iostream> using namespace std; class A{ public: virtual void func() { cout<<"A"<<endl; } }; class B:public A{ public: void func() { cout<<"B"<<endl; } void func(int a) { cout<<"B1"<<endl; } }; void test(A* p) { p->func(); } int main() { A *p = new B; //B*-->A*,吧子类指针转化为父类指针 // A *p = new C; p->func(); //B *q = new A;//A*-->B*,把父类指针转换为子类指针,不可以 test(p); B *q = new B; test(q); A *T = new B; T->func(); test(T); q->func(1); cout<<sizeof(B)<<endl; }
2.纯虚函数的实例:
#include <iostream> class A{ virtual void func() = 0; }; class B:public A{ void func() { } }; int main() { B a; }
3.虚析构实例:
#include <iostream> using namespace std; class A{ public: virtual ~A() { cout<<"A"<<endl; } }; class B:public A{ public: ~B() { cout<<"B"<<endl; } }; void test() { A* p = new B; delete p; } int main() { test(); }
12.异常
程序在运行时候会发生各种各样的预期之外的情况叫做异常
异常处理机制
try(检查)->throw(抛出)->catch(捕获)
格式:
void func() { if() { throw invalid_argument(""); } } try{ }catch (invalib_argument &ia) { is.waht() }
13.智能指针
1.shared_ptr 共享指针
1.shared_ptr 共享指针 template<class T> class shared_ptr{ public: shared_ptr(T *ptr) { p = ptr } shared_ptr(const shard_ptr<T> &obj) { p = obj.p; } ~Demo() { delete p; } //重载运算符 *T operator ->() { return p; } &T operator *() { return *p; } private: T *p; }
示例:
#include <iostream> #include <memory> using namespace std; class Demo{ public: Demo() //无返回值 可重载 构造对象时自动调用 { cout << __func__ << ":" <<__LINE__ <<endl; } virtual ~Demo() { cout << __func__ << ":" <<__LINE__ <<endl; } void prnmsg() { cout << __func__ << ":" <<__LINE__ <<endl; } }; int main() { shared_ptr<Demo> p(new Demo); //p指向Demo shared_ptr<Demo> pp = p; //重载->运算符 p->prnmsg(); pp->prnmsg(); (*p).prnmsg(); (*pp).prnmsg(); }
2.unique_ptr 独享指针
独享指针禁止拷贝构造
#include <iostream> #include <memory> using namespace std; class Demo{ public: Demo() //无返回值 可重载 构造对象时自动调用 { cout << __func__ << ":" <<__LINE__ <<endl; } virtual ~Demo() { cout << __func__ << ":" <<__LINE__ <<endl; } void prnmsg() { cout << __func__ << ":" <<__LINE__ <<endl; } }; int main() { unique_ptr<Demo> p(new Demo); //p指向Demo p->prnmsg(); (*p).prnmsg(); }
3.weak_ptr 弱指针
弱指针
和共享指针一起使用
14.标准模板库(STL)
是一种泛型编程
1.容器
优点: 1.可以不指定大小 2.随机访问方便 3.节省空间 缺点: 1.在内部进行删除插入效率低 2.
STL容器包括:
数组、链表、队列等等 能进行 增删查改
vector:
将元素置于一个动态数组加以管理,可以随机存取元素(索引)
deque
可以随机存取元素
list
不提供随机存储
2.迭代器
特殊的指针