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. 函数重载的要求

  1. 函数的参数个数不同
  2. 函数的参数类型不同
  3. 参数个数相同,但是顺序不同
  4. 返回值不作为函数重载的依据
// 函数重载的要求
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);
	}
};


在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值