基于C语言的C++学习(一)

基于C语言的C++学习(一)

这是作者的学习经历和学习笔记,如有错误,欢迎指正。
学习网址是黑马程序员的C++

const和指针

const修饰指针–常量指针

指针指向的地址可以改变,指针指向的地址里面的值不可以改

int a = 10;
int b = 20;
const int * p = &a;	//常量指针

*p = 30;	//错误
p = $b;		//正确

const修饰常量–指针常量

指针指向的地址不可以改,指针指向的地址的值可以改

int a = 10;
int b = 20;
int * const p = &a;	//指针常量

p = &b;		//错误
*p = 30;	//正确

const既修饰指针,又修饰常量

指针指向的地址和地址里面的值都不能改

int a = 10;
int b = 20;
const int * const p = &a;	

p = &b;		//错误
*p = 30;	//错误

C++核心编程

内存区分模型

  • 代码区:存放函数体的二进制代码,由操作系统进行管理
  • 全局区:存放全局变量和静态变量以及常量
  • 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
程序运行前

程序编译后,生成exe可执行文件,未执行该程序前分为两个区域

  • 代码区
    • 存放cpu执行的机器指令
    • 代码区是共享的,共享的目的是对于频繁执行的程序,只需要在内存中有一份代码即可
    • 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
  • 全局区
    • 全局变量和静态变量存放于此
    • 全局区还包含了常量区,字符串常量和其他常量
    • 该区域的数据在程序结束后由操作系统释放
程序运行后
  • 栈区
    • 由编译器自动分配释放,存放参数的参数值,局部变量等
    • 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
  • 堆区
    • 由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
    • 在C++中利用new关键字在堆区开辟内存

new运算符

new的基本用法

  • 作用

    1. 分配基本数据类型的内存

    2. 分配对象的内存

    3. 分配数组的内存(需用 delete[] 释放)

  • 语法

    new 数据类型

#include<iostream>
using namespace std;

int * func()
{
	int * p = new int(10);
	return p;
}

void test01()
{
	int * p = func();
	cout << *p << endl;
	cout << *p << endl;
	
	delete p;
	cout << *p << endl;	//会报错
}

int * func02()
{
	int * p = new int[10];
	return p;
}

void test02()
{
	int * arr = func();
	for(int i = 0; i < 10; i++)
	{
		arr[i] = i;
	}
	for(int i = 0; i < 10; i++0)
	{
		cout << arr[i] << endl;
	}
	
	delete[] arr;
	cout << arr << endl;	//会报错
}
int main()
{
	test01();
	test02();
	
	system("pause");
	return 0;
}
  1. 基本数据类型分配new int 分配一个 int 类型的内存空间,返回指向该空间的指针,使用 delete 释放。
  2. 对象分配new Student("张三", 20) 创建 Student 类的实例,会自动调用构造函数初始化对象,使用 delete 释放时会调用析构函数。
  3. 数组分配new int[5] 分配能存储 5 个 int 的数组,注意释放时必须使用 delete[] 而不是 delete,否则可能导致内存泄漏。

注意事项

  • 使用 new 分配的内存位于堆(heap)中,而不是栈(stack)中
  • 必须成对使用 new/deletenew[]/delete[],否则会导致内存泄漏
  • 可以使用 nullptr 检查指针是否有效(如 if (pInt != nullptr)
  • C++11 后推荐使用智能指针(如 unique_ptrshared_ptr)来自动管理内存,减少手动释放的风险

引用

引用的基本使用

  • 作用:给变量起别名
  • 语法:数据类型 &别名 = 原名
#include<iostream>
using namespace std;

int main()
{
	int a = 10;
	int &b = a;
	cout << a << endl;
	cout << b << endl;
	cout << &a << endl;
	cout << &b << endl;
	
	system("pause");
	return 0;
}

注意事项

  1. 引用必须初始化
  2. 引用一旦初始化后就不能更改

扩展

引用做函数参数
  • 作用:函数传参时,可以利用引用的技术让形参修饰实参
  • 优点:简化指针的使用
#include<iostream>
using namespace std;

void myswap01(int * a, int * b)	//地址传递
{
	int temp = *a;
	*a = *b;
	*b = temp;
}

void myswap02(int &a, int &b)	//引用传递
{
	int temp = a;
	a = b;
	b = temp;
}

int main()
{
	int a = 10;
	int b = 20;
	
	myswap01(&a, &b);
	myswap02(a, b);
	
	cout << a << endl;
	cout << b << endl;
	
	system("pause");
	return 0;
}
引用做函数返回值
  • 作用:引用可以作为函数的返回值存在
  • 注意:不要返回局部变量引用
  • 用法:函数调用可以作为左值
#include<iostream>
using namespace std;

int& test01()
{
	int a = 10;
	return a;
}

int& test02()
{
	static int a = 10;
	return a;
}

int main()
{
	int &ret = test01();
	cout << ret << endl;	
	cout << ret << endl;	//两次输出的值会不同

	int &ret02 = test02();
	cout << ret02 << endl;	
	cout << ret02 << endl;	//两次输出的值相同
	
	test022() = 1000;
	cout << ret02 << endl;	
	cout << ret02 << endl;	//两次输出的值相同,为1000
}
引用的本质

本质:引用的本质在C++中是一个常量指针

  • int& ref = a等于int* const ref = a
  • ref = 20等于*ref = 20
常量引用

作用:

  1. 常量引用主要用来修饰形参,防止误操作
  2. 允许传入常量或临时值
#include<iostream>
using namespace std;

void showvalue(const int & val)
{
	val = 1000;	//会报错,不能修改
	cout << "val = " << val << endl;
}

int main()
{
	int a = 10;
	showvalue(a);
	
	cout << "a = " << a << endl;
	
	system("pause");
	return 0;
}

const 引用只能接收可修改的左值(如变量),而 const 引用可以接收:

  • 变量(左值)
  • 常量(如 53.14 等右值)
  • 表达式的临时结果(如 a + b 的计算结果)

这极大地扩展了函数的适用范围:

void print(const int& x) {
    cout << x << endl;
}

int main() {
    int a = 10;
    print(a);       // 允许:变量(左值)
    print(20);      // 允许:常量(右值)
    print(a + 30);  // 允许:表达式临时结果(右值)
    return 0;
}

如果没有 const,上面后两种调用会编译报错。

函数高级

函数的默认参数

  • 函数可以在定义中写入形参的默认值,在后续的函数调用中若没有写入实参,则使默认值传入
  • 如果某个参数有了默认值,那么从左往右之后的参数都因该有默认值,即有默认值的参数放到右边
  • 函数的声明和定义不能同时有默认值
#include<iostream>
#include<string>
using namespace std;


int func(int a, int b = 20, int c = 30)
{
	return a + b + c;
}

int func02(int a = 20, int b, int c = 30)	//报错
{
	return a + b + c;
}

int func03(int a, int b = 20, int c = 30)int func03(int a, int b = 20, int c = 30)	//报错
{
	return a + b + c;
}

int main()
{
	cout << func(10) << endl;	//输出60
	cout << func(10, 40) << endl;	//输出80

	system("pause");
	return 0;
}

函数的占位参数

  • 作用:函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
  • 语法:返回值类型 函数名 (数据类型){}
#inc;ude<iostream>
using namespace std;

void func(int a, int)
{
	cout << "this is func" << endl;
}

void func02(int a, int = 10)
{
	cout << "this is func" << endl;
}

int main()
{
	func(10, 10);
	
	system("pause");
	return 0;
}

函数重载

  • 作用:函数名可以相同,提高复用性
  • 函数重载满足条件:
    • 同一个作用域下
    • 函数名称相同
    • 函数参数类型不同或者个数不同或者顺序不同
  • 注意:函数的返回值不可以作为函数重载的条件
#include<iostream>
using namespace std;

void func()
{
	cout << "func的调用" << endl;
}

void func(int a)
{
	cout << "func(int a)的调用" << endl;
}

void func(double a)
{
	cout << "func(double a)的调用" << endl;
}

void func(int a, double b)
{
	cout << "func(int a, double b)的调用" << endl;
}

void func(double a, int b)
{
	cout << "func(double a, int b)的调用" << endl;
}

int main()
{
	func();
	func(10);
	func(10.0);
	func(10, 10.0);
	func(10.0, 10);
	
	system("pause");
	return 0;
}
函数重载的注意事项
  • 引用作为重载条件
#include<iostream>
#include<string>
using namespace std;

void func(int &a)
{
	cout << "func(int &a)的调用" << endl;
}

void func(const int &a)
{
	cout << "func(const int &a)的调用" << endl;
}

int main()
{
	int a = 10;
	func(a);	//输出:func(int &a)的调用
	func(10);	//输出:func(const int &a)的调用
	
	system("pause");
	return 0;
}
  • 函数重载碰到默认参数
#include<iostream>
#include<string>
using namespace std;

void func(int a, int b = 10)
{
	cout << "func(int a, int b = 10)的调用" << endl;
}

void func(int a)
{
	cout << "func(int a)的调用" << endl;
}

int main()
{
	int a = 10;
	func(10);	//报错
	func(10, 10);	//输出:func(int a, int b = 10)的调用
	
	system("pause");
	return 0;
}

函数模板

函数模板(Function Template)是 C++ 中实现泛型编程的核心机制,它允许你定义一个通用的函数框架,支持多种数据类型,而无需为每种类型重复编写相同逻辑的代码。

基本语法

函数模板的定义格式如下:

template <typename T>  // 模板参数声明,T是类型占位符
返回类型 函数名(参数列表) {
    // 函数体(使用T作为类型)
}

其中 typename 也可以替换为 class(两者在函数模板中含义相同)。

简单示例:通用交换函数

#include <iostream>
using namespace std;

// 函数模板:交换任意类型的两个变量
template <typename T>  // 声明模板参数T
void swapValues(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

int main() {
    // 交换整数
    int x = 10, y = 20;
    swapValues(x, y);
    cout << "交换后:x=" << x << ", y=" << y << endl;  // 输出:x=20, y=10

    // 交换浮点数
    double f1 = 3.14, f2 = 6.28;
    swapValues(f1, f2);
    cout << "交换后:f1=" << f1 << ", f2=" << f2 << endl;  // 输出:f1=6.28, f2=3.14

    // 交换字符
    char c1 = 'A', c2 = 'B';
    swapValues(c1, c2);
    cout << "交换后:c1=" << c1 << ", c2=" << c2 << endl;  // 输出:c1=B, c2=A

    return 0;
}

函数模板的工作原理:

  1. 模板定义:编译器不会直接编译模板本身,而是将其视为 “代码生成器”。

  2. 实例化

    :当你用具体类型(如intdouble)调用模板函数时,编译器会根据类型自动生成对应版本的函数(称为模板实例)。

    • 例如调用 swapValues(x, y)xyint)时,编译器会生成一个参数为 int&swapValues 函数。
  3. 类型检查:编译器会确保模板中的操作对实际类型是合法的(如 T 类型必须支持赋值操作)。

模板的显式实例化与特化

  • 显式实例化:手动指定模板类型,强制编译器生成特定版本的函数:

  • // 显式实例化int类型的swapValues
    template void swapValues<int>(int& a, int& b);
    
  • 模板特化:为特定类型提供自定义实现(当通用模板不适用时):

    #include <iostream>
    #include <cstring> // 用于字符串比较函数strcmp
    using namespace std;
    
    // 通用模板:比较两个值的大小并返回较大者
    template <typename T>
    T maxValue(T a, T b) {
        cout << "使用通用模板比较:";
        return (a > b) ? a : b;
    }
    
    // 模板特化:专门处理const char*(字符串)类型
    template <>
    const char* maxValue<const char*>(const char* a, const char* b) {
        cout << "使用字符串特化模板比较:";
        // 字符串比较不能直接用>,需要用strcmp
        int result = strcmp(a, b);
        return (result > 0) ? a : b;
    }
    
    int main() {
        // 1. 比较整数(使用通用模板)
        int num1 = 10, num2 = 20;
        cout << maxValue(num1, num2) << endl;
    
        // 2. 比较浮点数(使用通用模板)
        double f1 = 3.14, f2 = 2.71;
        cout << maxValue(f1, f2) << endl;
    
        // 3. 比较字符串(使用特化模板)
        const char* str1 = "apple";
        const char* str2 = "banana";
        cout << maxValue(str1, str2) << endl;
    
        return 0;
    }
    

注意事项

  1. 模板定义通常放在头文件:因为编译器需要在调用处看到完整的模板定义才能实例化。
  2. 类型限制:模板中的操作必须对实际类型有效(如 + 操作对 int 有效,但对自定义对象可能需要重载 +)。
  3. 不允许自动类型转换:模板参数的类型必须严格匹配(除非使用显式指定类型)。
  4. 避免过度泛化:只在逻辑完全通用时使用模板,否则可能导致代码复杂。

函数模板是 C++ 泛型编程的基础,广泛用于标准库(如 std::vectorstd::sort 等),它能大幅减少重复代码,提高代码复用性和灵活性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值