目录
C++基础
输入输出
#include<iostream>
//cin cout 从内存中输入输出 分别是 istream类型 ostream类型
//c++的头文件库包含c语言的头文件库 可以写scanf和printf
using namespace std;
//namespace命名空间 std-标准
//使用标准命名空间 限制cin/cout的使用范围在std下
namespace S
{
int a;
}
int a, b;//全局
int main()
{
//cin 从控制台读入内容到变量。 cout从变量输出内容到控制台。
cin >>a >> b;
cout << a <<" " << b<<endl;//endl--换行
S::a = 4;//S是作用域
cout << a << " " << b << endl;
cout << S::a << " " << b << endl;
return 0;
}
bool类型
- bool类型 :是c++中一种整型类型 只占1字节 值仅有两个 false(0)和true(1)
- 非零的正负数都是1(false)
为什么只占一个字节?
答:1字节等于8位长的数据单位,大多数的计算机用1个字节表示1个字符、数字或其他字符,故1字节足够表示两个值了
#include<iostream>
using namespace std;
int main()
{
bool a1 = -1 ; //将被转换为true,非零正负值都转换为true。
bool a2 = 0; //将被转换为false
int b1 = true; //将被转换为1
int b2 = false; //将被转换为0
cout << a1 << endl;
cout << a2 << endl;
cout << b1 << endl;
cout << b2 << endl;
return 0;
}
new操作符
- 是C++中在堆区开辟数据的
- 语法:new+数据类型
- 利用new创建的数据会返回数据对应类型的指针
- 在堆区申请内存,释放时用delete 释放数组是用delete[]
- new申请数组时返回首元素的地址,申请int型变量返回的是这块地址
int main()
{
int* a1 = new int;//在堆区申请一块内存为int类型的内存 随机值
cout << a1 << endl;
delete a1;
int* a2 = new int();//在堆区申请一块内存为int类型的内存 值为0
cout << a2 << endl;
delete a2;
int* a3 = new int(4);//在堆区申请一块内存为int类型的内存 4
cout << a3 << endl;
delete a3;
int* a4 = new int [3];
for (int i = 0; i <3; i++)
cout << a4[i]<<" ";//在堆区申请一块内存为int类型的数组 值随机值
delete[]a4;//释放时不加[]会发生 内存泄漏(这块内存丢了,找不到)
int* a5 = new int[3] {1, 2, 3};//在堆区申请int类型数组 值为1 2 3
cout << a5;
delete[]a5;
cout<<a5;//报错,释放的空间无法找到并访问
return 0;
}
new和malloc的区别
new是在堆区申请内存的,如果给类或者结构体申请内存的话会优先调用malloc 再调用构造函数,
释放new申请的内存需要使用delete,释放数组需要delete[],delete会先调用析构函数在调用free,
当这个类的析构函数没有作用时,也可以使用free释放new申请的堆区空间
- 1.返回值 new返回值不需要强转 malloc返回值需要强转‘
- 2.名字:new是运算符可以重载 malloc是c语言库函数不可以重载
- 3.参数:new不需要传入具体字节个数 malloc需要
- 4.函数体:new先调用malloc再调用构造函数给成员变量赋值 delete先调用析构在调用free,malloc只分配堆区内存
- 5.申请内存失败:new会抛出异常 malloc返回空
malloc分配内存失败的情况:
(1) 参数不正确
int*p =(int *)malloc(-12);//不能为负数
(2) 堆区没足够的空间
引用
1.性质
- 作用是给变量起别名,引用必须初始化,且初始化不能为空,引用不能改变引用关系(底层是指针常量type *const pointer)),指针常量不可修改
- 引用可以作为函数的参数和函数返回值存在,但是引用不能接收局部变量 加static可接收
- 引用不占内存,函数传参时,利用引用的技术让实参和形参代表的是同一块内存空间
- 引用分为:左值引用,右值引用,万能引用(在左值引用前加const)。左值引用只能接收左值,右值引用只能接收右值,万能引用都能接收
- 左值是有名字有地址的变量,右值是无名字无地址的变量。匿名对象(只存在当前行,下一行就被释放)和常量都是右值
【注】 const修饰形参,防止形参改变实参
【问】在C++中引用不能返回局部变量的的原因有:
- 局部变量的生命周期在函数返回后结束。
- 但是引用的生命周期会延续到调用的上下文环境中。
- 这样就出现了一个引用指向的内存已经被释放的非法引用。使用这个无效的引用可能会导致程序崩溃或其他难以预料的后果。
2.参数传递有两种方式
- 值传递
void test01(int a, int b)//值传递不修改原参数的值
{
int c = a;
a = b;
b = c;
}
- 引用传递
void test02(int &a, int &b)
{
int c = a;
a = b;
b = c;
}
左值引用右值引用
#include<iostream>
using namespace std;
//返回局部变量引用
int& test01() {
int a = 10; //局部变量
return a;
}
//返回静态变量引用
int& test02() {
static int a = 20;
return a;
}
int main() {
//不能返回局部变量的引用
int& ref = test01();
cout << "ref = " << ref << endl;
cout << "ref = " << ref << endl;
/*有名字的变量是左值例如:a = 1 ,a为左值,例如: 2 ,2 是右值
左值引用只能接收左值,右值引用只能接收右值*/
int&& R = 2;//右值引用接收右值
int a = 2;
int& R1 = a; //左值引用只能接收左值
//int &&R2 = a; //报错
//int &R3 = 2; //报错
//万能引用:前面用const 修饰
const int& R4 = 2;
const int&& R5 = 3;
//如果函数做左值,那么必须返回引用
int& ref2 = test02();
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
test02() = 1000;
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
return 0;
}
3.匿名对象(右值)
- 没有名字的对象
- 只存在于当前行,到下一行就被释放掉了
- 函数以引用方式返回的值是左值 不引用返回的是匿名对象
4.引用和指针
- 都可以修改目标对象的值
- 都可以用于传递函数参数
- 引用必须在生命时初始化,指针可以随时初始化
函数参数默认值
c++中参数的形参列表中的形参是可以有默认值的
必须是从右向左依次赋默认值
函数在主函数下面定义需要声明 声明时给默认值,实现时不需要给了
//void fun(int a = 1, int b = 2, int c = 3)
//{
// cout << a << b << c << endl;;
//}
//函数在主函数下面定义需要声明 声明时给默认值,实现时不需要给
void fun(int a = 1, int b = 2, int c = 3);
int main()
{
fun(1);
fun(1, 22);
fun(1, 22, 333);
return 0;
}
void fun(int a, int b, int c)
{
cout << a << b << c << endl;;
}
函数重载
1.定义
在同一个作用域下,函数名字相同,参数类型 或 个数 或 顺序不同
【注】:函数的返回值不作为函数重载的条件
void fun() { cout << "无参" << endl; }
void fun(int a) { cout << "int" << endl; }
void fun(int a, double b) { cout << "int double" << endl; }
void fun(double a, int b) { cout << "double int" << endl; }
int main()
{
fun();
fun(2, 1.0);
fun(5);
fun(1.8, 2);
}
2.c语言为什么不能重载而c++可以?
C语言和c++的编译方式不同,c++中编译器编译后函数名是由原函数名+参数类型构成 ,参数不同就是函数名不同,调用时不会发生冲突
而C语言编译方式起完的名字还是自己 不能重载会发生冲突
3.函数重载的调用时期
函数在调用时 是在编译期间确定调用哪个重载函数的 ,根据实参类型确定的。编译器会选择一个最合适的函数来执行
4.extern "C" 声明全局变量函数
函数重载碰到函数默认参数
在一个类中,当无参数的构造函数和带默认参数的构造函数重载时,有可能产生二义性
//1.引用作为传递条件
void func(int& a)
{
cout << "func(int a)调用" << endl;
}
void func(const int& a)
{
cout << "func(const int &a)调用" << endl;
}
//2.函数重载碰到函数默认参数
void func2(int a, int b = 10)
{
cout << "fun2(int a,int b=10)调用" << endl;
}
void fun2(int a)
{
cout << "fun2(int a)调用" << endl;
}
int main()
{
int a;
func(a);//调用无const
func(10);//调用有const
func2(10);
return 0;
}
类和对象
1.面向对象三大特征 封装 继承 多态
c++-->万物皆对象 对象:凡是占有内存的 int a a是对象
2.属性是成员变量 行为是成员函数或者成员方法
3.在定义对象时,若定义的是指向此对象的指针变量,则访问此对象的成员时,不能用“.”操作符,而应该使用“->“操作符。如
封装
意义:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
【注】类内声明 类外实现时要加作用域
空类大小是1
设计一个类求圆的周长
class Circle
{
public:
double r;
void area()
{
cout << 2 * 3.14 * r;
}
};
int main()
{
Circle c;
c.r = 5.8;
c.area();
return 0;
}
设计一个类显示学生信息
#include<string>
class Student
{
public:
string name;
int sid;
void set_name(string n)
{
name = n;
}
//类内声明
void card();
};
//类外实现 要加作用域
void Student::card()
{
cout << "姓名:" << name << " " << "学号:" << sid;
}
int main()
{
Student s1;
s1.set_name("张三");
s1.sid = 202305;
s1.card();
}
类的构成--三种权限
public 公有权限 类内 子类 对象可以访问
protected 受保护权限 类内和子类能访问
private 私有权限 只有类内能访
【注】数据设置为私有 为了保证数据的准确性,提高安全性
#include<iostream>
using namespace std;
#include<string>
class Person
{
public://公有权限 类内 子类 对象可以访问
string name;
//name = "123";//没有申请空间不能访问
protected://受保护权限 类内和子类能访问
string car;
private://私有权限 类内能访问
string password;
public:
void set_car(string c)
{
car = c;
}
void set_password(string s)
{
password = s;
}
void printf()
{
cout << name << car << password;
}
void fun()
{
name = "张三";
car = "红旗";
password = "123654";
}
};
int main()
{
//创建对象时申请内存 给成员变量分配内存
Person p;
p.name = "李四";
p.fun();
p.set_car("劳斯莱斯");
p.set_password("666");
p.printf();
//p.car = "拖拉机";//受保护权限不能调用
}
类和结构体的区别
【问】c++中类和结构体的唯一区别?
默认的访问权限和继承权限不同,类的默认访问权限是私有的结构体是公有的
#include<iostream>
using namespace std;
#include <iomanip>
#include<cmath>
class C1//数据设置为私有 为了保证数据的准确性,提高安全
{
int m_A;//默认是私有权限
};
struct C2
{
int m_a;//默认是公共权限
};
int mian()
{
C1 c1;
c1.m_A = 10;//错误,访问权限是私有的
C2 c2;
c2.m_A = 10;//正确,访问权限是公共
system("pause");
}
对象的初始化和清理
构造函数和析构函数
构造函数
语法:类名+(){}
编译器提供的默认构造函数是空实现
构造函数可以是私有的
1.当写一个类时 c++的编译器会提供4个默认函数: 构造函数 析构函数 浅拷贝 赋值运算符
2.构造函数是一种特殊的成员函数
- 主要作用是在创建对象时用来给成员变量赋值 ,名字必须与类名相同 ,可以有任意类型的参数可以重载,但是不能有返回值
- 在创建对象的时候编译器自动调用构造函数 且只调用一次,如果没有实现构造函数编译器会提供默认构造函数
一个类的指针对象,如果分配空间的话,就会调用构造函数,在析构时要手动调用delete 如果没有分配就,不会调用。
#include<iostream>
using namespace std;
#include<string>
#include<iomanip>
class Pointer
{
int* p;
int a, b;
public:
Pointer()//构造函数没有返回值 //如果没有实现构造函数,编译器会提供一个默认的构造函数
{
//赋值:给已存在的变量一个值
//初始化:再创建一个变量时给他一个值
b = a;
a = 1;
cout << "无参构造" << a << b;;
}
Pointer(int n)
{
if (n > 0) p = new int[n];//在堆区申请n个大小为int类型的变量
cout << "有参构造" << endl;
}
};
int main()
{
Pointer p1;//在创建对象时编译器自动调用
Pointer p2(2);//调用有参构造 创建对象
Pointer p3();//编译器会认为是函数声明 Pointer是返回值类型 p3是函数名 ()是参数
}
构造函数的三种调用方式
int main()
{
//构造函数的分类调用
/*
括号法 显示法 隐式转换法(构造函数前面加explicit可以屏蔽隐式转换)
*/
Pointer a();//括号法
//显示法
Pointer b = Pointer(2);
//隐式转换法
Pointer c = 2;//将int类型通过构造函数 隐式转换成Pointer类型
}
析构函数
性质:
- 是用来释放成员变量指向的堆区内存的 名字和类名相同前面加~
- 没有参数和返回值 不能重载
- 在释放对象时编译器自动调用构造函数,且只调用一次如果没有实现析构函数编译器会提供默认的析构函数
class Pointer
{
int n;
int* p;
public:
Pointer()
{
}
Pointer(int n)
{
this->n = n;
p = new int[n];
}
~Pointer()
{
if (p)delete[]p;
}
};
int main()
{
Pointer p(4);//调用无参构造创建A类型的对象在堆区
Pointer* p1 = new Pointer;//无参
delete p1;
Pointer* p4 = new Pointer[3]{ Pointer(1),Pointer(2) };
delete[]p4;
}
delete先调用析构(内存空间)再调用free(free释放p自己)
new先调用malloc再调用构造函数
【注】当对象的生命周期结束时,析构函数会被自动调用
拷贝构造函数
定义:形参是本类对象的引用,通过已存在的对象初始化新的对象
拷贝构造参数必须是引用避免递归加const避免修改实参
常见的用处(调用时机):
- 对象以值的方式作为函数参数
- 对象以值的方式作为函数返回值
- 使用已存在的对象初始化新的对象
如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作
如果用户定义了拷贝构造,编译器不会提供其他构造函数
- 每个类都必须有一个拷贝构造函数。可以自己定义拷贝构造函数,用于按照需要初始化新对象;如果没有定义类的拷贝构造函数,系统就会自动生成一个默认拷贝构造函数,用于复制出与数据成员值完全相同的新对象。
编译器会提供默认的拷贝构造 浅拷贝 构造 拷贝构造 析构函数
#include<iostream>
using namespace std;
class A
{
public:
A() {};
A(const A& other)
{
cout << "拷贝构造" << endl;
}
};
A fun()
{
A a;//调用无参构造
return a;
}
void fun(A a)
{
}
int main()
{
fun();
A a;
fun(a);
A b(a);//已存在的对象初始化新的对象
A c = a;//隐式类型转换调用构造函数
}
#include<iostream>
using namespace std;
class Person
{
public:
int age;
public:
//无参构造
Person()
{
cout << "无参构造!" << endl;
}
//有参构造
Person(int a)
{
age = a;
cout << "有参构造" << endl;
}
//拷贝构造函数 参数为引用 避免递归 加const避免修改实参
//构造函数拷贝自己这种东西叫拷贝构造
Person(const Person& p)
{
age = p.age;
cout << "拷贝构造" << endl;
}
//析构函数
~Person()
{
cout << "析构函数!" << endl;
}
};
void test01()
{
Person p1(18);
//如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作
Person p2(p1);
cout << "p2的年龄为: " << p2.age << endl;
}
void test02()
{
//如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造
Person p1; //此时如果用户自己没有提供默认构造,会出错
Person p2(10); //用户提供的有参
Person p3(p2); //此时如果用户没有提供拷贝构造,编译器会提供
//如果用户提供拷贝构造,编译器不会提供其他构造函数
Person p4; //此时如果用户自己没有提供默认构造,会出错
Person p5(10); //此时如果用户自己没有提供有参,会出错
Person p6(p5); //用户自己提供拷贝构造
}
int main() {
test01();
return 0;
}
拷贝构造函数是通过已经存在的对象初始化新对象
1.什么时候会调用拷贝构造?
参数或返回值以值的方式传递时
2.参数是const A&other 万能引用,左值右值都可以接收
3.参数含有指针时用深拷贝,浅拷贝会导致内存泄漏,两个变量指向同一个位置,修改一个另一个也改变
深浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区申请新内存,进行拷贝操作
#include<iostream>
using namespace std;
class Person
{
public:
int age;
int* m_height = nullptr;
public:
//无参构造
Person()
{
cout << "无参构造!" << endl;
}
//有参构造
Person(int a,int height)
{
this->age = a;
this->m_height = new int(height);
cout << "有参构造" << endl;
}
//拷贝构造函数 参数为引用 避免递归 加const避免修改实参
Person(const Person& p)
{
age = p.age;
this->m_height = p.m_height;//浅拷贝
this->m_height = new int(*p.m_height);//深拷贝
cout << "拷贝构造" << endl;
}
//析构函数
~Person()
{
cout << "析构函数!" << endl;
if (m_height)
delete m_height;
}
};
int main() {
//编译器会提供默认的拷贝构造 浅拷贝 析构函数
//拷贝构造就是简单的赋值
//浅拷贝:不同对象里面的指针类型成员变量指向同一块堆区内存,会造成一块内存
//多次释放编译器会报错,其中一个对象修改了内存另一个对象也会被修改
Person p(16, 4);
Person p1 = p;
return 0;
}
#include<iostream>
#include<vector>
#include<string>
using namespace std;
class Person
{
int age;
int *m_height = nullptr;
string name;
public:
Person() { cout << "无参默认构造" << endl; }
Person(int age,string name,int height)
{
this->age = age;
this->name = name;
this->m_height = new int(height);
}
//参数为引用 避免递归,加const 避免修改实参
Person(const Person& other)
{
this->age = other.age;
this->name = other.name;
//this->m_height = other.m_height; 浅拷贝
//会导致两个对象里的两个成员变量指向同一个堆区内存,
//一个对象修改,另一个也随之修改
//下面是深拷贝
this->m_height = new int(*other.m_height);
}
~Person()
{
cout << "析构函数" << endl;
if (m_height)
{
delete m_height;
}
}
};
int main()
{
Person p(16,"张三",4);
Person p1 = p;
}
初始化参数列表
初始化参数列表只能在构造函数中使用,是给成员变量初始化的,初始化的顺序和参数列表的顺序无关,和成员变量的顺序一致(成员变量顺序决定初始化顺序),引用和常量在初始化参数列表中初始化
#include<iostream>
using namespace std;
class Person
{
int P_a, P_b, P_c;
int& P_e;
const int P_f;
public:
Person(int a, int b, int c) :P_a(c), P_b(P_c), P_c(c),P_e(a), P_f(b){};
void print()
{
cout << P_a << " " << P_b << " " << P_c << " " << P_e << " " << P_f;
}
};
int main()
{
Person p(1,2,30);
p.print();
return 0;
}
对象成员
类中的成员是其他一个类的对象,称该成员为 对象成员
构造顺序:对象成员的构造,在调用本类构造
析构顺序:与构造顺序相反
#include<iostream>
using namespace std;
const int N = 1e4 + 5;//数组的大小需要用常量定义
int dt[N][N];
class A
{
public:
A(int a)
{
cout << "A的构造" << endl;
}
~A()
{
cout << "A的析构" << endl;
}
};
class B
{
//没有无参构造,所以创建对象a要给个参数,在初始化参数列表初始化
A a;//构造函数是给对象初始化的,是给成员变量赋值的
public:
B(int b):a(b)
{
cout << "B的构造" << endl;
}
~B()
{
cout << "B的析构" << endl;
}
};
int main()
{
B b(2);
}
静态成员函数
特点:
- 所有对象共享一个静态成员变量
- 在编译阶段,主函数之前进行初始化
- 在类内声明 类外初始化
- 可以使用类名或对象名访问公有的静态成员变量
- 在发生继承时,静态成员变量不会被继承,父类子类共享同一个静态成员
- 静态成员变量不占对象的内存(静态成员变量在静态区,所有对象共同访问同一个)
静态成员变量在静态区,静态成员函数在代码区
- 静态数据成员的定义与普通数据成员相似,但前面要加上static关键字。
- 静态数据成员的初始化与普通数据成员不同。静态数据成员初始化应在类外单独进行,而且应在定义对象之前进行。一般在main()函数之前、类声明之后的特殊地带为它提供定义和初始化。
- 静态数据成员属于类(准确地说,是属于类中对象的集合),而不像普通数据成员那样属于某一对象,因此,可以使用“类名::”访问静态的数据成员。格式如下:类名::静态数据成员名。
- 静态数据成员与静态变量一样,是在编译时创建并初始化。它在该类的任何对象被建立之前就存在。因此,共有的静态数据成员可以在对象定义之前被访问。对象定以后,共有的静态数据成员也可以通过对象进行访问。
#include<iostream>
using namespace std;
class B
{
public:
B()
{
cout << "B的构造" << endl;
}
};
class A
{
public:
static int a;//类内声明 类外初始化
static B c;
int b = 0;
A()
{
a++;
b++;//所有对象共享同一个静态成员变量
}
};
int A::a = 0;
B A::c = B();
int main()
{
A a, b, c, d;
cout << a.a <<" " << b.a <<" " << c.a <<" " << d.a << endl;
cout << sizeof(a) << endl;
A::a = 1;
}
空指针访问成员函数
#include<iostream>
#include<algorithm>
using namespace std;
class A {
int a;
public:
void fun()
{
//this->a=3;
cout << "work" << endl;
}
};
int main()
{
//空指针可调用函数,但是函数中有静态成员变量时会报错
//因为this是空指针
A *a1=nullptr;
a1->fun();
return 0;
}