C++入门

一.基本知识

1.命名空间

(1)C的命名缺陷

原因:rand与库函数中的rand()函数,名字冲突了

(2)C++提出的解决方案 ——命名空间 

命名空间——命名空间域 :只影响使用,不影响生命周期,之前是全局还是全局变量

namespace AQueue //a的命名空间
{
	struct Node
	{
		struct Node* next;
		int val;
	};
	struct Queue
	{
		struct Node* head;
		struct Node* tail;
	};

	int x = 0;
}

namespace BList
{
	struct Node
	{
		struct Node* next;
		struct Node* prev;
		int val;
	};
	int x = 0;
}
这样a,b就不会因为都有Node而重命名了冲突了;

//命名空间与结构体不同之处在于不需要加";"; 命名空间可以嵌套;


int main()
{
	struct AQueue::Node node1;  
//AQueue 是命名空间, :: 域操作符 ,node1命名的名字 ,空代表全局域
	struct BList::Node node2;

	AQueue::x++;
	BList::x++;

	return 0;
}

using namespace std;  //加到全局变量的操作,std是库,std是自己封起来的
//本来命名空间AQueue,这样之后就相当于把AQueue加到全局变量里

2.C++输入输出

操作符: << 流插入  ; >> 流提取

int main()
{
	int n;
	cin >> n;  //流(输入)到n里去
	double* a = (double*)malloc(sizeof(double) * n);

	//C++可以自动识别类型
	for (int i = 0; i < n; i++)
	{
		cin >> a[i];   //这里就不需要写"%d"之类的了
	}
	for (int i = 0; i < n; i++)
	{
		cout << a[i] << endl;  //看成把a[i],endl流(输出)出去
		//控制精度还是用c :printf("%.2f",a[i]);
	}
	return 0;
}

3.缺省参数

注意:缺省值必须是全局变量或常量

//void fc(int a)可以写成这样:

void fc(int a = 0) 
{
	cout << a << endl;
}

void fcc(int a = 1, int b = 2, int c = 3)
{
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
}

int main()
{
	fc(1);  //当a传值时,用传的值
	fc( );  //当a没值时,用自己一开始的值

	fcc(4, 5, 6); 
	fcc(4, 5);
	fcc(4);
	fcc( );  
	//不能跳跃缺省: fcc( , ,6) 这样不可以

	return 0;
} 
函数的声明和定义不能同时缺省参数

void fcc(int a=10);

void fcc(int a=10)
{
    cout << a << endl ;
}

一定要是声明的时候给数,定义的时候不给。
eg:

void fcc(int a=10);

void fcc(int a)
{
    cout << a << endl ;
}

4.函数重载——允许同名函数(在同一作用域)

         C++在编译的时候会修饰这两个重载函数,使其有差别,就不会重定义了。

        *返回值不同,其他相同,不能构成重载,因为当不需要返回值时你也不知道调的哪个函数。

5. 引用——针对指针的缺陷

int main()
{
	int i = 0;
	int& k = i; 
    //相当于取别名,不会另开空间,(取别名k,指向i ),"int&" 是一个类型,同“int*”
	int j = i; //直接复制,而且另开一个空间

    int* p = &i;
	int*& rp = p; //任何类型都可以取别名,包括指针
	
	//只有在 int& 才是引用,其他时候都是取地址
	cout << &i << endl; 
	cout << &k << endl;
	cout << &j << endl;
	//i,k 地址一样

	k++; //i也++;
	j++;

	int& q = i;  //可以继续给本名取别名
	int& m = k;  //也可以继续给别名取别名,都是这同一个空间的名字

	return 0;
}

1.应用举例:

void Swap(int& x, int& y) 
//这样弄得话,形参是实参的别名,形参的改变就可以影响实参了
{
	int tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int i = 0;
	int j = 2;
	Swap(i, j);
	cout << i << endl;
	cout << j << endl;

	return 0;
}

2.引用返回:

1.减少拷贝;
2.调用者可以修改返回对象;


struct Ax
{
	int a[10];
	int i;
};

int& Posat(Ax& ax, int i)
{
	return ax.a[i];
}


int main()
{
	Ax ax;
	for (int i = 0; i < 10; i++)
	{
		Posat(ax, i) = i * 10; //引用的返回值可以被修改
	}
	for (int i = 0; i < 10; i++)
	{
		cout << Posat(ax, i) << " ";
	}
	cout << endl;

	return 0;
}

错误案例: 

   这个地方的 “&” ,相当于又给c取了别名ret,所以ret并不是的可以维持的变量,一出add函数就会被销毁掉;如果不用引用,而是int ret = Add(1,2) ,那么也不一定是对的,因为出函数的时候,可能这个栈帧会被销毁,c的拷贝保不保留不确定,所以最好的是int Add(int a,int b)。

 3.引用的权限问题

4.与指针的区别

6.内联函数——针对宏的缺点

 1.宏的缺点:

        ·  不能调试;

        ·  没有类型安全的检查

        · 有些场景下非常复杂,容易出错,不容易掌握

2.代码:

//继承了宏的优点:不需要栈帧;而且还能调试
inline int Add(int x, int y)
{
	int z = x + y;
	return z;
}

int main()
{
	int ret = Add(6,2);
	Add(3, 4);
	cout << ret << endl;
	return 0;
}

3.内联会导致编译后空间变大(相当于宏的直接展开,而普通的函数调用就用一行)

7. auto类型——自动确定类型

int main()
{
	int a = 0;
	auto b = a;  //自动确定类型
	auto c = &a;  //自动确定了类型
	
	cout << typeid(b).name() << endl;  //typeid(b).name() 可以返回括号内变量的类型 
	cout << typeid(a).name() << endl;

}

可以用来简化代码:当类型名很长的时候可以用

 (1)范围for:

int main()
{
	int array[] = { 1,2,3,4,5,6 };

	//范围for——语法糖
	//意思:自动依次取数组中的数据赋值给e对象,自动判断结束
	for (auto e : array)  //for(int x : array) 也可以,不过一般都是auto
	{
		e = e * 2;
		cout << e << endl;
	}
	//现在数组是{1,2,3,...}
	
	//要改变数组内容则要加引用,因为这个e只是数组中元素的拷贝,改变e没有用
	for (auto& e : array)
	{
		e = e * 2;
		cout << e << endl;
	}
	//现在数组是{2,4,6,...}

	return 0;
}

(2)注意:

1.auto不能做形参: test(auto a);
2.auto不能声明数组:  auto a[10];

8.nullptr与NULL的区别:

          因为某些历史遗留问题,C++中的NULL被定义为0(#define NULL 0),在某些场景下,NULL可能会被识别成整数0(int类型)。

         出了这个BUG之后C++11打入了新补丁,用nullptr来表示空指针。所以最好空指针用nullptr。

二.类和对象 

1.类与结构体的区别

       ① 类里面可以有数据,这个数据叫做成员变量

       ②类里面还可以定义函数

2.类

(1)类的定义

//类 ——长得很像结构体
struct Stack
{
	//成员变量
	int* a;
	int size;
	int capacity;

	//成员函数
	void Init(int n=4) //缺省参数
	{
		a = (int*)malloc(sizeof(int) * n);
		capacity = n;
		size = 0;
	}
	void Push(int x)
	{
		a[size++] = x;
	}

};

int main()
{
	Stack st;
	st.Init();  //类中函数的调用
	st.Push(1);

	return 0;
}
//虽然struct可以定义类,但是C++最好还是用class来定义

若声明与定义分开,则需要在定义时加上命名空间

头文件:
struct Stack
{
	int* a;
	int size;
	int capacity;
	void Init(int n = 4);
	void Push(int x);
};


.cpp文件

void Stack::Init(int n = 4) //需要加 “Stack::”这样的命名空间
{
	a = (int*)malloc(sizeof(int) * n);
	capacity = n;
	size = 0;
}
void Stack::Push(int x)
{
	a[size++] = x;
}

(2) 访问限定符

class Stack
{
private:
	int* a;
	int size;
public:
	int capacity; //公有
};

 (3)封装

 (4)类的实例化


//类就像是一副设计图,实例化才是建造的成果
class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

//private: 
	int _year;  //这些都是声明,因为并没有开空间
	int _month;
	int _day;
};

int main()
{
	//类对象实例化——给他开辟空间
	Date d1;
	Date d2;

类和结构体一样,也要考虑内存对齐的规则:
	d1.Init(2023, 2, 2);
	d1._year++;

	cout << sizeof(d1) << endl;  
	//结果是12字节,结论:成员变量存在类中,成员函数不是
原因:每个对象成员变量是不一样的,所以需要独立储存;而每个对象调用成员函数是一样的,需要放到共区域(代码段)
	return 0;
}
class A2
{
public:
	void f1()
	{}
};

int main()
{
	A2 aa1;
	A2 aa2;

	return 0;
}

只有成员函数的类大小是一字节,这一个字节不存储有效数据,仅用来占位,表示他被实例化过;

(5)this指针——编译器对函数对象指向的处理

1.this指针存在哪里? ——存在栈上,因为this是隐含的形参 /在vs下,存在ecx寄存器中

2.空指针问题:

(1)func()正常运行是因为空指针并没有被解引用,因为func()只是个普通的函数,不需要到指向的对象中去找什么东西,也就不需要解引用了。

(2)而第二个会去解引用,因为有"this->_year"等这些操作,而this指针接收的地址是空指针,所以空指针被解引用了。

(3)这个虽然有解引用*,但是func()并不是类里的成员变量,而是成员函数,而与成员变量无关的成员函数存在公共代码段中,所以不会报错,这里的指针是传给this指针的

(6)与C的区别

1.C语言数据与方法是分离的;C++的数据与方法是封装到一个类里面

2.C语言数据访问控制是自由的,不受限制;C++的数据可以控制访问方式(public),可以防止不能改的被改了

(7)静态成员变量与静态成员函数

class A
{
public:
	A(int a = 0)
	{
		++count;
	}

	//静态成员函数——没有this指针
	static int GetCount()
	{
		//_a++;   //静态成员函数不能访问非静态成员,也是因为没有this指针
		return count;
	}
	 
private:
	//static并不是属于某个对象,而是属于所有对象,属于整个类
	static int count; //声明

};

int A::count = 0; //定义,初始化

int main()
{
	A aa;
	cout<< A::GetCount() << endl;  //类能找到count
	cout << aa.GetCount() << endl;  //类的实例也能找到count
	return 0;
}

(8)匿名对象

class Solution
{
public:
	Solution(int a = 0)
	{
		++count;
	}

	static int GetCount()
	{
		//_a++;   
		return count;
	}
	 
private:
	static int count; //声明

};


int main()
{
	Solution s;  //普通对象
	Solution s1(); //可能爆错,因为与函数太像


	//匿名对象,生命周期只有这一行
	Solution();
	cout << Solution().GetCount() << endl;  //一次性的,随用随释放,很方便

}

(9)友元——可以突破封装

 1.友元函数(在类外面写,在类里面用)

2.友元类

(10) 内部类

   这里的这个B类, 跟A的空间是独立的,只是受A的类域限制,B只是写在A的里面,但是实际上跟写在全局当中是没有区别的。

性质:内部类天生是外面类的友元:可以直接在B中访问A的私有对象

(11)关于编译器的优化

1.接受返回对象的时候,尽量用拷贝构造的方式接收,不要赋值接收。


A fun3()
{
	A aa;
	return aa;
}

int main()
{
	//拷贝构造接收
	A aa1 = fun3();


	//赋值接收
	A aa2;
	aa2 = fun3();  
}

2.函数中返回对象时,尽量返回匿名对象。

3.传参尽量用" const& "引用传参。

三.特殊的类函数

注意:无论什么类型的指针,都是内置类型,总之就是个指针(int* 与 stack* 都是内置类型)

1.构造函数

 *当实例化后会自动调用构造函数

初始化链表(特殊的构造函数)
有三类变量必须在初始化列表进行初始化
1.const类型 const int _x;
2.引用类型  int& ref;
3.没有默认构造的自定义类型成员对象 B _bb;(但是B里没有bb对应的构造函数)

*初始化初始值的顺序与初始化顺序无关,与类的对象的声明顺序有关

class A
{
	//1.那个对象调用构造函数,初始化列表就是它所有成员变量定义的位置
	//2.不管是否显示在初始化列表,那么编译器每个变量都会初始化列表定义初始化
	A() :_x(1), _a2(1),_ref(1),_bb(0)//优先初始化链表,其次是缺省值,最后还没定义就是随机值了
	{
		_a1++;
		_a2++;
	}

private:
	int _a1 = 1;
	int _a2 = 2;
	const int _x;
	int& _ref;
	B _bb;
}

int main()
{
	A aa; //这是个对象整体的定义
	//而对于const这样类型的对象,必须要在定义的时候初始化,所以要给每个成员变量找一个定义的位置

	return 0;
}

2.析构函数

class stack
{
public:
	stack() //构造函数名字必须跟类名字相同
	{
		_a = nullptr;
		_size = _capacity = 0;
	}

	stack(int n)  //构成函数重载
	{
		_a = (int*)malloc(sizeof(int) * n);
		_capacity = n;
		_size = 0;
	}
	//这样写可以把初始化函数省掉,免得每次都忘
	
	~stack()
	{
		free(_a);
		_a = nullptr;
		_size = _capacity = 0;
	}

private:
	int* _a;
	int _size;
	int _capacity;
};

//
int main()
{
	stack st; //实例化后自动调用构造函数
	stack stt(4); //当构造函数需要传参的时候这个样写

	return 0;
}

3. 拷贝构造函数

    这里是浅拷贝,像这里假如stack直接拷贝,那么a指针会直接拷贝给新的stack,这样两个栈都指向同一块空间,就出错了。

class date
{
public:
	date(int y = 1, int m = 1,int d = 1) //构造函数
	{
		_year = y;
		_month = m;
		_day = d;
	}

	date(const date& d)  //拷贝构造函数
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	date GetAfterXDay(int x) //返回类型是date ,所以return *this
	{
		//date tmp(*this);
		date tmp = *this;  //这里的*this就是d1,因为this指针指向d1;
		//拷贝构造,先产生一个tmp的中间变量,这样就不会影响d1了

		tmp._day = tmp._day + x;
		while (tmp._day > GetMonthDay(tmp._year, tmp._month)) //如果天大于当前月,就进位
		{
			//进位
			tmp._day = tmp._day - GetMonthDay(tmp._year, tmp._month);
			tmp._month++;

			if (tmp._month == 13)
			{
				tmp._year++;
				tmp._month = 1;
			}
		}
		//return *this;  
		//this是这个对象的指针,*this就是指的这个对象,而这里就是需要返回date类型,返回自己
		return tmp;
	}


	void print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

	int GetMonthDay(int year, int month)
	{
		int monthArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
		if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
		{
			return 29;
		}
		return monthArray[month];
	}

private:
	int _year;
	int _month;
	int _day;
};


int main()
{
	date d1(2023, 2, 3);
	date d2 = d1.GetAfterXDay(100);

	d1.print();
	d2.print();

	return 0;
}

应用:日期加法:


class date
{
public:
	date(int y = 1, int m = 1,int d = 1) //构造函数
	{
		_year = y;
		_month = m;
		_day = d;
	}

	date(const date& d)  //拷贝构造函数
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	date GetAfterXDay(int x) //返回类型是date ,所以return *this
	{
		//date tmp(*this);
		date tmp = *this;  //这里的*this就是d1,因为this指针指向d1;
		//拷贝构造,先产生一个tmp的中间变量,这样就不会影响d1了

		tmp._day = tmp._day + x;
		while (tmp._day > GetMonthDay(tmp._year, tmp._month)) //如果天大于当前月,就进位
		{
			//进位
			tmp._day = tmp._day - GetMonthDay(tmp._year, tmp._month);
			tmp._month++;

			if (tmp._month == 13)
			{
				tmp._year++;
				tmp._month = 1;
			}
		}
		//return *this;  
		//this是这个对象的指针,*this就是指的这个对象,而这里就是需要返回date类型,返回自己
		return tmp;
	}

	void print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

	int GetMonthDay(int year, int month)
	{
		int monthArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
		if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
		{
			return 29;
		}
		return monthArray[month];
	}

private:
	int _year;
	int _month;
	int _day;
};


int main()
{
	date d1(2023, 2, 3);
	date d2 = d1.GetAfterXDay(100);

	d1.print();
	d2.print();

	return 0;
}

4. 运算符重载

class date
{
public:
	void print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

	//d1==d2
	//相当于 d1.operator==(d2)
	bool operator==(const date& d) //因为是成员函数,所以只需要传别人的那个就行了
	{
		return _year == _year  //this->_year==d._year (this指向d1,d就是d2)
			&& _month == _month
			&& _day == _day;
	}

	//d1<d2
	bool operator<(const date& d)
	{
		if (_year < d._year)  //这里实际是if (this->_year < d._year)
		{
			return true;
		}
		else if(_year==d._year&&_month<d._month)
		{
			return true;
		}
		else if (_year == d._year && _month < d._month && _day < d._day)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	//d1<=d2
	bool operator<=(const date& d)
	{
		return *this < d || *this == d;
	}

	//d1>d2
	bool operator>(const date& d)
	{
		return !(*this <= d);  //大于是小于等于的逻辑取反
	}

	//d1>=d2
	bool operator>=(const date& d)
	{
		return !(*this < d);   //大于等于是小于的逻辑取反
	}

	//d1!=d2
	bool operator!=(const date& d)
	{
		return !(*this == d);
	}

	//只要实现小于等于和小于就可以了,其他的都可以直接取反




    date& operator+=(int day)  //日期加等天数 
	{
        if (day < 0)
		{
			*this -= -day;  //当d1 += -100时
			return *this;
		}

		_day = _day + day;
		while (_day > GetMonthDay(_year, _month))
		{
			_day = _day - GetMonthDay(_year, _month);
			_month++;
			if (_month == 13)
			{
				_year++;
				_month = 1;
			}
		}
		return *this;
	}

	//d1+100 日期加天数,由加等推加
	date operator+(int day)
	{
		date tmp(*this);  //拷贝一个tmp出来
		tmp += day;  //让tmp去加等,d1不改变
		return tmp;
	}

    //d1+=100
	date& operator+=(int day) //由加推加等
	{
		*this = *this + day;
		return *this;
	}



	//++d1 先++,后使用
	date& operator++()
	{
		*this += 1;
		return *this;
	}


	//d1++ 先使用,后++
	date operator++(int) //int为了构成重载
	{
		date tmp(*this);
		*this += 1;
		return tmp; //返回之前的值
	}



	//d1 -=  (_day会改变)
	date& operator-=(int day)
	{
		if (day < 0) //针对d1 -= -100
		{
			*this += -day;
			return *this;
		}
		_day -= day;
		while (_day <= 0)
		{
			--_month;
			if (_month == 0)
			{
				--_year;
				_month = 12;
			}
			_day += GetMonthDay(_year, _month);
		}
		return *this;
	}

	//d1-day
	date operator-(int day)
	{
		date tmp = *this;
		tmp -= day;
		return tmp;
	}

	//--d1 
	date& operator--()
	{
		*this -= 1;
		return *this;
	}

	//d1--
	date operator--(int)
	{
		date tmp = *this;
		*this -= 1;
		return tmp;
	}

    //d1-d2
	int operator-(const date& d)
	{
		date max = *this;
		date min = d;
		int flag = 1;

		if (*this < d)
		{
			max = d;
			min = *this;
			flag = -1;
		}
		
		int n = 0;
		while (min != max)
		{
			++min;
			++n;
		}
		return n * flag; 
	}


private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};


//全局函数(类外面的)的运算符重载写法
bool operator==(const date& d1, const date& d2) //有几个参数,就有几个操作符
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}


int main()
{
	date d1;
	date d2;

	cout << (d1 == d2) << endl; //最后也会被转换成调 d1.operator==(d2);
	
	cout << (d1 < d2) << endl;
	cout << d1.operator<(d2) << endl;

	//自定义流输出
    ostream& operator<<(ostream& out, const date & d) //这里的out相当于cout
    {
        out << d._year << "年" << d._month << "月" << d._day <<"日"<< endl;
	    return 0;
    }

    //自定义流输入
    istream& operator>>(istream& in, date& d)  //这里的in相当于cin
    {
	    in >> d._year >> d._month >> d._day;
	    return in;
    } 


	return 0;
}
要不要加const:
class date
{
public:
	void print()const 
    //这里的const修饰的是*this,const只能加在外面,this类型变成const this*
    //所以内部不改变成员变量的成员函数,最好都加上const,变成const this*
    {
        cout<<_year<<endl;
    }


private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};


int main()
{
	
	return 0;
}
赋值运算符:
class date
{
public:
	//赋值运算符 “=”
	//d1=d2 ,把d2赋给d1
	//这里的&:出了作用域,*this还在,如果是传值返回(date),那么会返回一个他的拷贝,会浪费空间
	//而如果是引用(date&),就不会浪费空间,而且出了作用域*this还在,也不会丢
	date& operator=(const date& d) 
	{ 
		if (*this != d)  //防止d1=d1这种
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}

		return *this;  
		//返回*this是为了支持连续赋值,如:d3=d1=d2,从右往左赋值,d1=d2这个操作符的返回值是d1
	}

private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};


int main()
{
	date d1;
	date d2;

	date d4(d1);
	date d3 = d1; //这些是拷贝构造,d3是刚定义的
	
	d2 = d1; //这个是赋值重载,两个都是已经定义好的对象

	return 0;
}

cin与cout就是运算符重载:

5.默认成员函数

1.默认构造函数:

        当你不写构造函数的时候,C++会自己编写一个无参数的默认构造函数,把成员变量都初始化为0,但是这里针对的成员变量只有自定义类型(node,queue,link等),而不会对基本(内置)类型初始化(int,char,等)。

//例:两个栈构成队列
class queue
{
public:
	//默认生成了构造函数
	void push(int x)  //其他函数
	{
		//...
	}
	stack _a_stack;
	stack _b_stack; //这里的a栈和b栈就会被初始化,*a=NULL,size=capacity=0
};

 2.默认析构函数:

        跟默认构造函数一样,C++会自己编写一个无参的默认析构函数,而这个函数只会对自定义类型(node,queue,link等)产生影响,而不会对改变(内置)类型。

注意:在C++11之后打了补丁,只要在类对象的声明的时候给缺省值,就可以和自定义类型一起被默认构造/析构函数初始化或改变了。

class date
{
public:

private:
	//这就是在声明位置给缺省值
	int _year = 1;
	int _month = 1;
	int _day = 1;
	//如果你没写构造函数,那么就会用这个缺省值来给成员变量初始化
};

后定义的数据/函数,先析构

3.默认拷贝构造函数——(值拷贝)浅拷贝(一个字节一个字节的拷贝过来)

        默认拷贝构造函数只可以处理内置基本数据类型,即你不自己编写,也可以实现(int,char等的拷贝)

class date
{
public:
	date(int y = 1, int m = 1,int d = 1) //构造函数
	{
		_year = y;
		_month = m;
		_day = d;
	}

	//date(const date& d)  //拷贝构造函数
	//{
	//	_year = d._year;
	//	_month = d._month;
	//	_day = d._day;
	//}

	void print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};


int main()
{
	date d1(2023, 2, 3);
	date d2 = d1;
	date d3(d1);

	d1.print();
	d2.print();
	d3.print();
	return 0;
}

 4.默认赋值重载

  取地址重载——自定义类型的取地址可以不应编写,直接用:

 

四.动态分配内存管理

1.C/C++内存分布

    局部变量在栈上,malloc,new开的在堆,每次程序结束都会清理栈上的内存,不会清理堆上的内存。 

2.C++动态内存管理

int main()
{
	//int* p1 = (int*)malloc(sizeof(int));
	int* p1 = new int; //C++的new是个操作符
	int* pp1 = new int(0); //可以这样进行初始化  //如果这里是自定义类型,那么需要写构造函数
	delete p1; //也是操作符,不需要加括号

	//多个对象
	//int* p2 = (int*)malloc(sizeof(int)*10);
	int* p2 = new int[10]; //就开了10个int空间
	int* pp2 = new int[10]{ 1,2,3,4 }; //初始化数组
	delete[] p2;//要加方括号

	

	//A* p3 = (A*)malloc(sizeof(A));
	A* p3 = new A(1);
	delete p3;

	return 0;
}

 new除了会开辟空间还会调用构造函数,而且new失败了不会返回空,会直接抛异常
 delete会在销毁空间前调用析构函数

C++/C不要交叉使用

3.operator new与operator delete函数

 

 

4.定位new—— 对一块已有的空间进行初始化 

int main()
{
	A aa;
	A* p1 = (A*)malloc(sizeof(A));
	
	//对一块已有的空间进行初始化 
	new(p1)A(1);   //A(1)是调用了构造函数
	return 0;
}

五.模板初阶——泛型编程

1.模板的原理

//template<class T> 
template<typename T>  //没有“;”,用class和typename都行
void swap(T &x, T &y) //T是一个总定义的类型名
{
	T tmp = x;
	x = y;
	y = tmp;
}

template<class X,class Y>  //定义多个参数

int main()
{
	int a = 1, b = 2;
	swap(a, b);
	double c = 1.1, d = 2.22;
	swap(c, d);
	//ab,cd调用的不是同一个函数,是模板实例化出的两个不同函数

	return 0;
}

 2.显示实例化

 一个是手动类型转换,一个是隐式类型转换

eg:

template<class T>
class stack
{
public:
	stack(int capacity = 4)
	{
		_a = new T[capacity];
		_top = 0;
		_capacity = capacity;
	}

	~stack()
	{
		delete[] _a;
		_capacity = _top = 0;
	}

private:
	T* _a;   //这里的T是下面的 int或double
	size_t _top;
	size_t _capacity;
};

int main()
{
	stack<int> st1;   //int
	stack<double> st2;   //double
	//自定义类型必须要显示实例化

	return 0;
} 

六.其他库函数

1.大小写

大小写判断

#include<bits/stdc++.h> 
//#include<cctype>
using namespace std;
int main()
{
	char ch1='a';
	char ch2='A';
	if(islower(ch1)) cout<<"小写"<<endl;
	if(isupper(ch2)) cout<<"大写"<<endl;
 	
	return 0;
}

大小写转换 

#include<bits/stdc++.h>
using namespace std;
int main()
{
	char ch1='a';
	char ch2='A';
	char ch3=toupper(ch1);
	cout<<ch3<<endl;
	cout<<toupper(ch1)<<endl;
	cout<<tolower(ch2)<<endl;
 	
	return 0;
}

如果是其他字符则不进行操作

2.二分查找

int main()
{
	vector<int> nums={1,3,5,7,9};
	int target=5;
	bool binary_search=(nums.begin(),nums.end(),target);  //二分查找函数
	
	return 0;
}

3.sort排序 

4. unique去重

注意:因为unique只能去重 相邻元素,所以必须先排序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

对玛导至昏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值