习题描述
cout << "a = " << a << endl;
标识符 第一个字符必须为字母或下划线
cout << (int)ch << endl; //查看字符a对应的ASCII码
sizeof( 数据类型 / 变量)
float f1 = 3.14f;//注意这里加了个f 这样加的好处是之后不用再进行转换,一步到位
//ch = “abcde”; //错误,不可以用双引号
//ch = ‘abcde’; //错误,单引号内只能引用一个字符
作用:用于表示一些不能显示出来的ASCII字符 \n \ \t
C++风格字符串: string 变量名 = “字符串值”
bool类型占1个字节大小
C++风格字符串,需要加入头文件==#include==
语法: cin >> 变量
Int型除法不保留小数
与&& 或||
表达式1 ? 表达式2 :表达式3
如果表达式1的值为真,执行表达式2,并返回表达式2的结果;
如果表达式1的值为假,执行表达式3,并返回表达式3的结果。
与while循环区别在于,do…while先执行一次循环语句,再判断循环条件
break使用的时机:
出现在switch条件语句中,作用是终止case并跳出switch
出现在循环语句中,作用是跳出当前的循环语句
出现在嵌套循环中,跳出最近的内层循环语句
continue语句
作用:在循环语句中,跳过本次循环中余下尚未执行的语句,继续执行下一次循环
如果{}内不足10个数据,剩余数据用0补全
int score2[10] = { 100, 90,80,70,60,50,40,30,20,10 };
数组名用途
同理二维数组
值传递时,==如果形参发生,并不会影响实参
函数的声明可以多次,但是函数的定义只能有一次
函数分文件编写一般有4个步骤
创建后缀名为.h的头文件
创建后缀名为.cpp的源文件
在头文件中写函数的声明
在源文件中写函数的定义
#include “swap.h”
指针
普通变量存放的是数据,指针变量存放的是地址
指针变量可以通过" * "操作符,操作指针变量指向的内存空间,这个过程称为解引用
空指针指向的内存是不可以访问的
野指针:指针变量指向非法的内存空间
所有指针类型在32位操作系统下是4个字节
因为存的是地址
32位指一次能处理32为数据,一般来讲,处理位数和地址总线和数据总线位数会做一致,方便操作
故32位的地址是4个字节
指针
常量指针
//const修饰的是指针,指针指向可以改,指针指向的值不可以更改
const int * p1 = &a;
指针常量
int * const p2 = &a;
//const修饰的是常量,指针指向不可以改,指针指向的值可以更改
即修饰指针又修饰常量
const int * const p3 = &a;
指针遍历数组cout << *p << endl; p++;
地址传递
void swap2(int * p1, int *p2)
swap2(&a, &b); //地址传递会改变实参
结构体 结构体数组
指针访问结构体是通过 -> 操作符可以访问成员
int * arr 也可以写为int arr[] 当数组名传入到函数作为参数时,被退化为指向首元素的指针
add->man[add->flag ].m_name =name;//这里出错了,前面用->后面用.
system(“cls”);//清屏函数
意义
程序执行时
int* func()
{
//指针本质是局部变量,放在栈上,指针保存的数据是放在堆区
int* a = new int(10);
return a;
}
int main() {
int *p = func();
//数组写法
int* arr = new int[10];
cout << *p << endl;
cout << *p << endl;
delete p;
//释放数组 delete 后加 []
delete[] arr;
return 0;
}
程序运行前
代码区:
存放 CPU 执行的机器指令
代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
全局区:
全局变量和静态变量存放在此.
全局区还包含了常量区, 字符串常量和其他常量也存放在此.
该区域的数据在程序结束后由操作系统释放
意义
注意事项
//返回静态变量引用
int& test02() {
static int a = 20;
return a;
}
int main(){
//如果函数做左值,那么必须返回引用
int& ref2 = test02();
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
test02() = 1000;
cout << "ref2 = " << ref2 << endl;
}
常量引用
//引用使用的场景,通常用来修饰形参
void showValue(const int& v) {
//v += 10;
cout << v << endl;
}
默认参数
//2. 如果函数声明有默认值,函数实现的时候就不能有默认参数
int func2(int a = 10, int b = 10);
int func2(int a, int b) {
return a + b;
}
函数重载
条件
三种权限
struct和class的区别
成员属性设置为私有
构造函数和析构函数
语法
class Person
{
public:
//构造函数
/*
1. 构造函数,没有返回值也不写void
2. 函数名称与类名相同
3. 构造函数可以有参数,因此可以发生重载
4. 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
*/
Person()
{
cout << "Person的构造函数调用" << endl;
}
//析构函数
/*
6. 析构函数,没有返回值也不写void
7. 函数名称与类名相同,在名称前加上符号 ~
8. 析构函数不可以有参数,因此不可以发生重载
9. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
*/
~Person()
{
cout << "Person的析构函数调用" << endl;
}
};
//1、构造函数分类
// 按照参数分类分为 有参和无参构造 无参又称为默认构造函数
// 按照类型分类分为 普通构造和拷贝构造
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int a) {
age = a;
cout << "有参构造函数!" << endl;
}
//拷贝构造函数
Person(const Person& p) {
age = p.age;
cout << "拷贝构造函数!" << endl;
}
//析构函数
~Person() {
cout << "析构函数!" << endl;
}
public:
int age;
};
//2、构造函数的调用
//调用无参构造函数
void test01() {
Person p; //调用无参构造函数
}
//调用有参的构造函数
void test02() {
//2.1 括号法,常用
Person p1(10);
//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明
//Person p2();
//2.2 显式法
Person p2 = Person(10);
Person p3 = Person(p2);
//Person(10)单独写就是匿名对象 当前行结束之后,马上析构
//2.3 隐式转换法
Person p4 = 10; // Person p4 = Person(10);
Person p5 = p4; // Person p5 = Person(p4);
//注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明
//Person p5(p4);
}
int main() {
test01();
//test02();
system("pause");
return 0;
}
三种方式
class Person {
public:
Person() {
cout << "无参构造函数!" << endl;
mAge = 0;
}
Person(int age) {
cout << "有参构造函数!" << endl;
mAge = age;
}
Person(const Person& p) {
cout << "拷贝构造函数!" << endl;
mAge = p.mAge;
}
//析构函数在释放内存之前调用
~Person() {
cout << "析构函数!" << endl;
}
public:
int mAge;
};
//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01() {
Person man(100); //p对象已经创建完毕
Person newman(man); //调用拷贝构造函数
Person newman2 = man; //拷贝构造
//Person newman3;
//newman3 = man; //不是调用拷贝构造函数,赋值操作
}
//2. 值传递的方式给函数参数传值
//相当于Person p1 = p;
void doWork(Person p1) {}
void test02() {
Person p; //无参构造函数
doWork(p);
}
//3. 以值方式返回局部对象
Person doWork2()
{
Person p1;
cout << (int *)&p1 << endl;
return p1;
}
void test03()
{
Person p = doWork2();
cout << (int *)&p << endl;
}
int main() {
//test01();
//test02();
test03();
system("pause");
return 0;
}
概念
简易理解
最简单直接的解释大概就是,浅拷贝实际上是对类成员的引用,深拷贝是对类成员的复制并且重新分配了内存。
所谓拷贝就是平常意义的复制,至于深浅,就因为一个东西:指针拷贝的时候,如果有指针,那么也就是对指针的拷贝,指针怎么拷贝?指针本质也就是一个存储地址的整形,所以拷贝的时候,也就是把指针本身进行复制,这样就导致一个问题:本体与复制体的指针成员实际是一个值(地址值),那么他们会操作同一个地址的内存内容,在析构的时候,就出现问题了,本体或者复制体进行析构的时候,将指针成员kill了,即把指针成员对应的地址内容kill掉了,那么另外一个复制体或者本体的指针成员怎么办????野指针啊!就因为这么个原因,为了让程序猿对这个问题加深认知,就出现了两个名词:深拷贝、浅拷贝!实际并没有什么深拷贝和浅拷贝,出现这两个词的原因也就是为了让你随时记住指针问题!万恶的指针!如果你对指针有着极其深刻的认知,随时能注意指针的动向,那么深拷贝、浅拷贝?忽悠小孩子的啦~
文件创建快捷方式是浅拷贝
文件复制文件是深拷贝
浅拷贝的文件被删除快捷方式失效
深拷贝的源文件被删除,备份文件仍有
class Person {
public:
传统方式初始化
//Person(int a, int b, int c) {
// m_A = a;
// m_B = b;
// m_C = c;
//}
//初始化列表方式初始化
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}
void PrintPerson() {
cout << "mA:" << m_A << endl;
cout << "mB:" << m_B << endl;
cout << "mC:" << m_C << endl;
}
private:
int m_A;
int m_B;
int m_C;
};
int main() {
Person p(1, 2, 3);
p.PrintPerson();
system("pause");
return 0;
}
概念
静态成员分为:
成员变量和成员函数分开存储
class Person {
public:
Person() {
mA = 0;
}
//非静态成员变量占对象空间
int mA;
//静态成员变量不占对象空间
static int mB;
//函数也不占对象空间,所有函数共享一个函数实例
void func() {
cout << "mA:" << this->mA << endl;
}
//静态成员函数也不占对象空间
static void sfunc() {
}
};
int main() {
cout << sizeof(Person) << endl;
system("pause");
return 0;
}
本质
用途
this指针的用途:
常函数:
常对象:
三种实现
对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
int a=10,b=10;
int c=a+b;
person p1,p2,p3;
p3=p1+p2;//编译器会不知道如何实现,所以可以定义一个类的
//函数实现加号运算,本质就是调用这个函数,这是编译器简化了这个写法
//成员函数实现 + 号运算符重载
Person operator+(const Person& p) {
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
//全局函数实现 + 号运算符重载
Person operator+(const Person& p1, const Person& p2) {
Person temp(0, 0);
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
//运算符重载 可以发生函数重载
Person operator+(const Person& p2, int val)
{
Person temp;
temp.m_A = p2.m_A + val;
temp.m_B = p2.m_B + val;
return temp;
}
作用:可以输出自定义数据类型
//全局函数实现左移重载
//ostream对象只能有一个
ostream& operator<<(ostream& out, Person& p) {
out << "a:" << p.m_A << " b:" << p.m_B;
return out;
}
/*
out cout都没有差,因为引用相当于取别名
cout << p1 << "hello world" << endl; //链式编程
链式编程在于每次返回都是同一个对象,故需要返回输出流
*/
/*
返回类型为引用,这样才能进行连续递增操作
注意后置递增的写法
*/
//前置++
MyInteger& operator++() {
//先++
m_Num++;
//再返回
return *this;
}
//后置++
MyInteger operator++(int) {
//先返回
MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
m_Num++;
return temp;
}
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
所以需要定义赋值运算符
简要
class 子类 : 继承方式 父类
继承中构造和析构顺序
继承同名成员处理方式
继承同名静态成员处理方式
菱形继承
多态分为两类
静态多态和动态多态区别:
多态满足条件
多态使用条件
重写:函数返回值类型 函数名 参数列表 完全一致称为重写
虚函数
其实就是变成指针
//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
virtual void speak()
{
cout << "动物在说话" << endl;
}
多态的优点:
来源
当类中有了纯虚函数,这个类也称为抽象类
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
抽象类特点:
问题
总结:
虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
拥有纯虚析构函数的类也属于抽象类
简介
//利用模板提供通用的交换函数
template<typename T>
void mySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
//利用模板实现交换
//1、自动类型推导
mySwap(a, b);
//2、显示指定类型
mySwap<int>(a, b);
注意事项:
自动类型推导,必须推导出一致的数据类型T,才可以使用
模板必须要确定出T的数据类型,才可以使用
普通函数与函数模板区别:
普通函数与函数模板的调用规则
调用规则如下:
STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
主要前4个
https://www.cnblogs.com/howo/p/8531449.html
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
虚函数:其实就是变成指针
//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
virtual void speak()
{
cout << "动物在说话" << endl;
}
问题
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
void test01()
{
Animal *animal = new Cat(“Tom”);
animal->Speak();
//通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏
//怎么解决?给基类增加一个虚析构函数
//虚析构函数就是用来解决通过父类指针释放子类对象
delete animal;
}
为什么无法调用子类代码
多态满足条件
子类继承父类后,当创建子类对象,也会调用父类的构造函数
构造函数在创建对象的时候调用,先调用父类,再子类,析构相反
(1)当父类的指针new一个子类的对象时,
父类析构不是虚析构,则delete的时候不调用子类的,只是调用父类的析构函数,如果是virtual的析构函数,则先子类之后父类
(2)当子类的指针new一个子类的对象时,构造析构都会调用父类
c/c++ 堆区程序员分配的内存,不释放,在程序结束时,系统一定会回收内存吗?
类中只要有一个纯虚函数就称为抽象类
//抽象类无法实例化对象
//子类必须重写父类中的纯虚函数,否则也属于抽象类
内存泄漏
所谓拷贝就是平常意义的复制,至于深浅,就因为一个东西:指针拷贝的时候,如果有指针,那么也就是对指针的拷贝,指针怎么拷贝?指针本质也就是一个存储地址的整形,所以拷贝的时候,也就是把指针本身进行复制,这样就导致一个问题:本体与复制体的指针成员实际是一个值(地址值),那么他们会操作同一个地址的内存内容,在析构的时候,就出现问题了,本体或者复制体进行析构的时候,将指针成员kill了,即把指针成员对应的地址内容kill掉了,那么另外一个复制体或者本体的指针成员怎么办????野指针啊!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hf60tTzZ-1617283322973)(C:\Users\wangxiaobing\AppData\Roaming\Typora\typora-user-images\image-20210315185239494.png)]
举例 :五子棋
举例:洗衣服
优缺点
面向过程
面向对象
https://www.runoob.com/cprogramming/c-unions.html
#include
#include
union Data
{
int i;
float f;
char str[20];
};
int main( )
{
union Data data;
data.i = 10;
data.f = 220.5;
strcpy( data.str, "C Programming");
printf( "data.i : %d\n", data.i);
printf( "data.f : %f\n", data.f);
printf( "data.str : %s\n", data.str);
return 0;
}
data.i : 1917853763
data.f : 4122360580327794860452759994368.000000
data.str : C Programming
在这里,我们可以看到共用体的 i 和 f 成员的值有损坏,因为最后赋给变量的值占用了内存位置,这也是 str 成员能够完好输出的原因。
常函数:
常对象:
常数据成员
C语言中的struct与C++中的struct的区别表现在以下3个方面:
在C++中 struct和class唯一的区别就在于 默认的访问权限不同
优点
缺点
程序可能难理解 C语言的缺点主要表现在数据的封装性上,这一点使得C在数据的安全性上有很大缺陷,这也是C和C++的一大区别。
C语言表达方面的自由会增加风险。尤其是C语言对指针的使用。
C语言的语法限制不太严格,对变量的类型约束不严格,影响程序的安全性,对数组下标越界不作检查等。
在 C++运算符集合中,有一些运算符是不允许被重载的。这种限制是出于安全方面的考虑,可防止错误和混乱。
(1)不能改变 C++内部数据类型(如 int,float 等)的运算符。
(2)不能重载‘.’,因为‘.’在类中对任何成员都有意义,已经成为标准用法。
(3)不能重载目前 C++运算符集合中没有的符号,如#,@,$等。原因有两点,一是难以理解,二是难以确定优先级。
(4)对已经存在的运算符进行重载时,不能改变优先级规则,否则将引起混乱。
在C++中 struct和class唯一的区别就在于 默认的访问权限不同
优点
缺点
程序可能难理解 C语言的缺点主要表现在数据的封装性上,这一点使得C在数据的安全性上有很大缺陷,这也是C和C++的一大区别。
C语言表达方面的自由会增加风险。尤其是C语言对指针的使用。
C语言的语法限制不太严格,对变量的类型约束不严格,影响程序的安全性,对数组下标越界不作检查等。
在 C++运算符集合中,有一些运算符是不允许被重载的。这种限制是出于安全方面的考虑,可防止错误和混乱。
(1)不能改变 C++内部数据类型(如 int,float 等)的运算符。
(2)不能重载‘.’,因为‘.’在类中对任何成员都有意义,已经成为标准用法。
(3)不能重载目前 C++运算符集合中没有的符号,如#,@,$等。原因有两点,一是难以理解,二是难以确定优先级。
(4)对已经存在的运算符进行重载时,不能改变优先级规则,否则将引起混乱。