C++第一天
01. 动态内存分配
// malloc 和 new 有什么区别
// - malloc 代表的是一个库函数, new 是一个运算符,且是一个关键字
// - malloc 申请时传入的是字节数,new 申请时传入的是个数
// - malloc 的返回值永远是 void*,new 的返回值是指向申请类型的指针
// - [ new 会调用构造函数,malloc 不会调用 ]
// free 和 delete 的区别
// - free 是一个库函数,delete 既是关键字又是运算符
// - [ delete 会调用析构函数,free 不会调用 ]
#include <stdlib.h>
int main()
{
// 1.1 申请一个单位大小的堆空间
int* MallocInt = (int*)malloc(sizeof(int));
// 1.2 申请多个单位大小的堆空间
int* MallocBuf = (int*)malloc(sizeof(int) * 10);
// 1.3 不管是一个还是多个释放堆空间的写法一致
free(MallocBuf);
free(MallocInt);
// 1.1 申请一个单位大小的堆空间
int* NewInt = new int;
// 1.2 申请多个单位大小的堆空间
int* NewArr = new int[10]{ 0 };
// 1.3 释放使用 new 申请的一个单位
delete NewInt;
// 1.4 释放使用 new[] 申请的多个单位
delete[] NewArr;
// 为了维护程序的稳定性,使用 new 申请的就用 delete 释放
// 使用 new [] 申请的页必须要使用 delete[] 进行释放
return 0;
}
02. 函数重载和名称粉碎
// 想要在 C 语言中实现多个计算不同类型相加的函数
// 就必须要需要定义多个不同名称的函数,因为 C 语言
// 函数的名称具有唯一性。在 C++ 中提供了函数重载的
// 机制,允许多个函数具有相同的函数名称。
// 如果在 C 语言中编写了同名函数,会产生下面的报错
// error C2371: “add”: 重定义;不同的基类型
// 如果有函数或者变量声明了但是没有定义,就会报错:
// fatal error LNK1120: 无法解析的外部命令
// C++ 的内部为了支持函数重载,提供了名称粉碎机制,
// 在编译器内部根据函数的参数和返回值实现了不同的名
// 称, 这个名称旨在编译器内部看得到,用户不用在意
// “int add(int,int)” (?add@@YAHHH@Z)
// “double add(double,double)” (?add@@YANNN@Z)
// 在 C++ 中通过 extern “C” 可以取消名称粉碎
// 如果只声明没有定义
extern "C" int add(int a, int b)
{
return a + b;
}
double add(double a, double b)
{
return a + b;
}
int main()
{
// 当有多个同名函数时,C++会根据具体传入的参数
// 来确定调用的是哪一个函数
add(1, 1); // -> int, int
add(1.1, 1.1); // -> double, double
return 0;
}
03. 函数重载的要求
- 函数的参数个数不同
- 函数的参数类型不同
- 参数个数相同,但是顺序不同
- 返回值不作为函数重载的依据
// 函数重载的要求
void test(int n) { } // 一个参数
// 1. 函数的参数个数不同
void test(int n, double m) { } // 两个参数
// 2. 函数的参数类型不同
void test(double n) { } // 带一个 double 参数
// 3. 参数个数相同,但是顺序不同
void test(double n, int m) { } // double, int
// 4. 返回值不作为函数重载的依据
// int test(int n) {}
int main()
{
test(1); // -> int
test(1, 1.1); // -> int, double
test(1.1); // -> double
test(1.1, 1); // -> double, int
return 0;
}
04. 使用默认参数
// 使用默认参数: 当调用函数时,如果有一个值会经常
// 性的被作为参数传递给函数,就可以为它设置一个默认
// 参数,比如下面的例子用于求圆的面积,在计算过程中
// PI 的精度一般会取小数点后两位,但是在一些特定的
// 情况下,需要取更高的精度,就可以设置默认参数
double s(double r, double PI = 3.14)
{
return PI * r * r;
}
int main()
{
s(10); // 没有提供 PI,默认使用3.14
s(10, 3.1415926); // 为了提高精度,可以自己传入更准确的值
return 0;
}
05. 使用默认参数的要求
// 1. 如果只存在函数的定义,那么默认参数可以直接写在定义中
// 2. 如果既存在声明,又存在定义,通常会被默认参数写在声明中
// 3. 当同时存在函数重载和默认参数时,可能会产生错误
/ 4. 函数设置默认参数的顺序,必须是从右往左的
// 1. 如果只存在函数的定义,那么默认参数可以直接写在定义中
// - 当函数的调用位于函数定义的下方时,可以不存在函数的声明
void test1(int n = 10) { }
// 2. 如果既存在声明,又存在定义,通常会被默认参数写在声明中
void test2(int n = 10); // 声明
// 当声明和定义同时存在默认参数,会产生错误: 重定义默认参数
void test2(int n0) { } // 定义
// 3. 当同时存在函数重载和默认参数时,可能会产生错误
void test1() { };
// 4. 函数设置默认参数的顺序,必须是从右往左的
// 在这里,如果想给 m 设置默认参数,就必须给 no 设置
void test3(int m = 10, int n = 20, int o = 30) { };
int main()
{
// void test1(int) 既接受一个参数,也可以[没有参数]
// void test1() 本身就[没有参数]
// test1();
test2();
return 0;
}
06. 使用引用和要求
// 1. 引用的使用必须进行初始化, int& 小二; 是错误的
// 2. 引用和被引用对象的地址相同
// 3. 修改其中的任意一个,都会影响到对方
// 4. 引用一经初始化,就不能引用其它变量
#include <stdio.h>
// 引用只是给一个已存在的变量取别名,不会产生新的变量
int main()
{
// 一个叫做小明的变量
int 王二 = 0, 周二 = 0;
// 定义一个引用(小名),引用了王二
// 这个时候,小二和王二指的都是同一个变量
// 1. 引用的使用必须进行初始化, int& 小二; 是错误的
int& 小二 = 王二;
// 2. 引用和被引用对象的地址相同
printf("&小二 = %p, &王二 = %p\n\n", &小二, &王二);
// 3. 修改其中的任意一个,都会影响到对方
小二 = 100;
printf("小二 = %d, 王二 = %d\n\n", 小二, 王二);
// 4. 引用一经初始化,就不能引用其它变量
小二 = 周二; // 是将周二的值(0)赋值给了小二(王二)
printf("&小二 = %p, &王二 = %p, &周二 = %p\n", &小二, &王二, &周二);
printf("小二 = %d, 王二 = %d, 周二 = %d\n", 小二, 王二, 周二);
return 0;
}
07. 引用的使用场景
// C++ 的传参方式
// - 引用(推荐),指针,值
// 引用和指针的区别
// - 引用访问一个变量是直接访问,而指针里面需要保存变量的地址,所以是间接访问
// - 引用是一个变量的别名,本身不单独分配自己的内存空间,它不是一个单独的变量,而指针有自己的内存空间
// - 引用一经初始化不能再引用其它变量,而指针可以
// 因为修改引用就是修改被引用的对象,所以引用通常
// 可以代替指针,完成通过修改形参影响实参的操作
// C++ 的传参方式
// - 引用(推荐),指针,值
// 引用和指针的区别
// - 引用访问一个变量是直接访问,而指针里面需要保存变量的地址,所以是间接访问
// - 引用是一个变量的别名,本身不单独分配自己的内存空间,它不是一个单独的变量,而指针有自己的内存空间
// - 引用一经初始化不能再引用其它变量,而指针可以
// 引用也可以改变实参,使用简单,推荐使用
void swap(int& x, int& y)
{
int z = x;
x = y;
y = z;
}
// 通过指针可以改变实参,但是比较复杂
void swap(int* x, int* y)
{
int z = *x;
*x = *y;
*y = z;
}
int main()
{
int n = 100;
int m = 200;
swap(n, m);
return 0;
}
08. C++ 中的输入输出
系统默认命名空间具有public属性
std:: 是个名称空间标示符,C++标准库中的函数或者对象都是在命名空间std中定义的,所以我们要使用标准函数库中的函数或对象都要使用std来限定。
输出的三种形式:
1.开头写using std::cout;
2.每次cout前面加上std::
3.开头写using namespace std;
(这种相当于该命名空间是c++的系统命名空间,而且之后所有的函数或对象都用std里边的)
// 1. 包含输入输出必须的头文件
#include <iostream>
// std 是名称空间的名字,如果在一个函数或者
// 变量前添加了这个说明 std:: 就表示使用的
// 是这个名称空间中的函数或变量。名称空间存在
// 的意义就是区分不同人编写的同名函数。
// 除了下面的使用方式,还可以使用更简单的方式
// 直接使用 using namespace std; 指令
// 还可以对单独的某一个函数或变量使用 using
// using std::endl; 表示 endlo 在 std 作用域中
int main()
{
int number = 0;
char str[100] = { 0 };
// 2. 使用 cin 对目标数据进行输入 >>
std::cin >> number >> str;
// 3. 使用 cout 将目标数据进行打印
std::cout << number << std::endl << str;
// 和 C 语言的输出方式相比,不需要过于关注
// 数据的类型,cin\cout 会根据传入的数值
// 类型进行相应的格式化
return 0;
}
09. 格式化输入输出
// 想要在C++中进行格式化输出输出操作,必须包含头文件
#include <iomanip>
#include <iostream>
using namespace std;
int main()
{
// 指定输出整数类型的进制数
cout << dec << 10 << endl; // 10
cout << hex << 10 << endl; // 16
cout << oct << 10 << endl; // 8
// 指定要输出的字符占用的宽度
cout << "*" << "1" << "*" << endl;
cout << "*" << setw(10) << "1" << "*" << endl;
// 指定用于填充位置的字符样式
cout << "*" << setw(10) << "1" << "*" << endl;
cout << "*" << setw(10) << setfill('0') << "1" << "*" << endl;
// 修改默认的对齐方式
cout << "*" << setiosflags(ios::left) << setw(10) << "1" << "*" << endl;
cout << "*" << setiosflags(ios::right) << setw(10) << "1" << "*" << endl;
// 如果使用格式化的输出输入比较多,那么推荐使用 C 语言方式
return 0;
}
0A. 打印三角形
#include <iomanip>
#include <iostream>
using namespace std;
/*
____* // 4 1
___*** // 3 3
__***** // 2 5
_******* // 1 7
__***** // 2 5
___*** // 3 3
____* // 4 1
*/
int main()
{
// 循环出菱形的前 4 行,i 表示的就是行
for (int i = 0; i < 4; ++i)
{
cout << setw(4 - i) << setfill('_') << "";
cout << setw(2*i+1) << setfill('*') << "" << endl;
}
for (int i = 2; i >= 0; --i)
{
cout << setw(4 - i) << setfill('_') << "";
cout << setw(2 * i + 1) << setfill('*') << "" << endl;
}
return 0;
}
0B. 使用类的方式
在C语言中,struct 中不能放函数
想要操作这个结构体,我们需要提供函数,推荐使用引用作为参数
原因是如果使用值传递,结构体越大,耗费的空间也会越大,如果
使用指针,会让程序更难维护,可读性降低。
#include <iostream>
using namespace std;
// 需求: 要求实现一个结构体,并且提供操作结构体的相关函数
typedef struct _Point2D
{
int x;
int y;
} Point2D;
void ShowPoint(Point2D& point)
{
// 输出坐标的内容
printf("(%d, %d)\n", point.x, point.y);
}
// 使用类(class)的方式实现上面的功能,类名通常以C开头
class CPoint2D
{
public:
// 一个点的坐标
int x;
int y;
// 将操作数据的函数写在这个类中
// 函数没有参数,但是可以直接访
// 问到 x 和 y
void show()
{
// 函数和xy都在 CPoint2D 作用域中
// 所以可以直接进行访问
printf("(%d, %d)\n", x, y);
}
};
int main()
{
// 创建并初始化结构体变量
Point2D StructPoint = { 1, 2 };
// 缺点就是数据和函数没有特定的关联
ShowPoint(StructPoint);
// 创建一个类对象(变量),进行输出
CPoint2D ClassPoint = { 1, 2 };
// show 是属于类的,数据也在类中,他们之间存在关联
ClassPoint.show();
return 0;
}
0C. 使用和定义类对象
#include <iostream>
using namespace std;
class CLocation
{
public:
// 成员函数的定义,也可以只写声明,但是必须要在类外定义
void init(int nNumA, int nNumB)
{
m_X = nNumA;
m_Y = nNumB;
}
int getx() { return m_X; }
int gety() { return m_Y; }
private:
// 定义的数据区域
int m_X, m_Y; //member date
};
int main()
{
// 1. 必须先通过类名定义一个对象(变量)
CLocation Object;
// 2. 通过类提供的一个接口(公有函数)进行初始化
Object.init(10, 20);
// 3. 再次通过成员函数访问数据
printf("%d, %d\n", Object.getx(), Object.gety());
return 0;
}
0D. 类的访问属性
访问权限控制符:
可以写在任意位置,
并且数量也没有要求,可以有多个也可以没有
公有的数据即使在类外也可以被访问 私有的和受保护的在类外不能被直接访问
private 和 Protected 在继承时会存在区别, 目前从使用和定义上没有任何的区别
#include <iostream>
class CObj
{
public:
int PublicNumber;
private:
// 通常将不想在类外访问的数据设置成私有的
int PrivateNumber;
protected:
int ProtectedNumber;
public:
void Print()
{
// 不论是什么类型的变量,都可以在
// 类的成员函数中访问到。
PublicNumber = 10;
PrivateNumber = 10;
ProtectedNumber = 10;
}
// 公有函数,可以在类外被直接访问
int GetPrivateNumber()
{
// 类内,可以访问任何的数据
return PrivateNumber;
}
};
int main()
{
CObj obj;
// 公有的数据即使在类外也可以被访问
obj.PublicNumber = 10;
// 私有的和受保护的在类外不能被直接访问
// obj.PrivateNumber = 10;
// obj.ProtectedNumber = 10;
// private 和 Protected 在继承时会存在区别,
// 目前从使用和定义上没有任何的区别
// 通常会提供一个接口(public 函数)专门用于访问或需修改私有的数据
// 输出的值没有意义,因为数据没有被初始化
printf("%d\n", obj.GetPrivateNumber());
// 【通常会提供一个公有的函数访问或者修改私有的数据】
return 0;
}
0E. 成员函数定义方式-函数内
#include <iostream>
// 实现一个日期类,在类内定义成员函数
class CDate
{
private:
// 定义不想直接被类外访问到的数据
int m_Year, m_Month, m_Day;
private:
// 判断一年是否是闰年,这个函数不需要在类外调用,可以是 private
bool IsLeapYear()
{
if (m_Year % 4 == 0 && m_Year % 100 != 0
|| m_Year % 400 == 0)
return true;
else
return false;
}
public:
// 提供函数用于操作年月日
void set(int y = 1900, int m = 1, int d = 1)
{
m_Year = y;
m_Month = m;
m_Day = d;
}
// 输出当前的日期,并且输出当前是否是闰年
void show()
{
printf("%d 年 %d 月 %d 日",
m_Year, m_Month, m_Day);
if (IsLeapYear())
printf(", 当前是闰年\n");
else
printf(", 当前不是闰年\n");
}
};
int main()
{
CDate date;
// 设置年月日
date.set(2008, 9, 2);
date.show();
return 0;
}
0F. 成员函数定义方式-类外
// 在类外定义成员函数
为了说明当前函数是一个成员函数,需要指定作用域
添加了作用域之后,默认的查找范围就在作用域中了
#include <iostream>
// 实现一个日期类,在类内定义成员函数
class CDate
{
private:
// 定义不想直接被类外访问到的数据
int m_Year, m_Month, m_Day;
private:
// 判断一年是否是闰年,这个函数不需要在类外调用,可以是 private
bool IsLeapYear();
public:
// 提供函数用于操作年月日
void set(int y = 1900, int m = 1, int d = 1);
// 输出当前的日期,并且输出当前是否是闰年
void show();
};
// 在类外定义成员函数
// 为了说明当前函数是一个成员函数,需要指定作用域
// 添加了作用域之后,默认的查找范围就在作用域中了
bool CDate::IsLeapYear()
{
if (m_Year % 4 == 0 && m_Year % 100 != 0
|| m_Year % 400 == 0)
return true;
else
return false;
}
// 提供函数用于操作年月日
void CDate::set(int y, int m, int d)
{
m_Year = y;
m_Month = m;
m_Day = d;
}
// 输出当前的日期,并且输出当前是否是闰年
void CDate::show()
{
printf("%d 年 %d 月 %d 日",
m_Year, m_Month, m_Day);
if (IsLeapYear())
printf(", 当前是闰年\n");
else
printf(", 当前不是闰年\n");
}
int main()
{
CDate date;
// 设置年月日
date.set(2008, 9, 2);
date.show();
return 0;
}
10. 成员函数定义方式-不同文件
// 所以想要使用这个类的文件,直接包含头文件就可以了
// 所以想要使用这个类的文件,直接包含头文件就可以了
#include "CDate.h"
int main()
{
CDate date1;
CDate date2;
// 设置年月日
date1.set(2008, 9, 2);
date1.show();
return 0;
}
11. this 指针的使用
C++ 中的结构体和类的区别
在C++中结构体实际上也是类,只是默认的访问方式不同
公有函数(接口),实际上每一个成员函数都有一个默认的 this 指针
在类内访问数据时,默认都会有一个 this 指针
this 默认就是指向当前对象(调用这个函数的对象)的指针
class Sample { };
void func(int Sample)
{ //形参屏蔽了类名
Sample++; //形参自增运算
// Sample 会被认为是一个变量
// Sample a;
//访问类名需加class
class Sample a; //访问类名需加class
}
#include <iostream>
// C++ 中的结构体和类的区别
// 在C++中结构体实际上也是类,只是默认的访问方式不同
class CTest1
{
public:
// 类的默认访问属性是 private
int numberA;
void show() { }
};
struct CTest2
{
public:
// 结构体的默认访问方式是 public
int numberA;
void show() { }
};
class CObj
{
public:
// 数据成员
int number = 0;
// 公有函数(接口),实际上每一个成员函数都有一个默认的 this 指针
// void set(CObj* this, int n)
void set(int n)
{
// 如何知道 number 是哪一个对象的 number
// 在类内访问数据时,默认都会有一个 this 指针
this->number = n;
// this 默认就是指向当前对象(调用这个函数的对象)的指针
}
};
int main()
{
// 使用类
CTest1 test1;
test1.numberA = 10;
// 使用结构体
CTest2 test2;
test2.numberA = 20;
CObj obj1;
CObj obj2;
obj1.set(10);
// 实际的调用是 set(&obj2, 10);
obj2.set(10);
return 0;
}
// 注意加分号
class Sample { };
void func(int Sample)
{ //形参屏蔽了类名
Sample++; //形参自增运算
// Sample 会被认为是一个变量
// Sample a;
//访问类名需加class
class Sample a; //访问类名需加class
}
int Sample = 0;
class Sample
{
void func()
{
// :: 表示使用全局范围的 Sample
printf("%d", ::Sample);
}
};