主要参考:黑马程序员C++课程
1. new
C++利用new在堆区开辟数据,由delete手动释放。
语法:new 数据类型
返回:该数据对应类型的指针
int* func() {
int* p = new int[10];
for (int i = 0; i < 10; i++) {
p[i] = i + 1;
}
return p;
}
int main()
{
int* p = func();
for (int i = 0; i < 10; i++) {
cout << p[i] << endl;
}
delete[] p; //p变为野指针
//delete释放一个变量,delete[]释放数组
return 0;
}
2. 引用
给变量起别名。引用的本质也是指针。
语法:数据类型 &别名=原名
int a = 10;
int& b = a; //引用必须初始化,之后不可改变即b再作为c的别名
//引用后,对ab的操作等同
//区别于值传递和地址传递,作为函数参数可以使用引用传递
void swap(int &a, int &b) { //这里的&a其实就是a的别名
int temp = a;
a = b;
b = temp;
return ;
}
int main() {
int a = 10, b = 20;
swap(a, b);
cout << a << b << endl;
return 0;
}
//引用作为函数的返回值
int& func() {
static int a = 10;
//这里加static原因是引用返回值的函数不能返回局部变量
//加了static的变量存放在全局区,程序结束才释放。
return a;
}
int main() {
int& b = func();
cout << b << endl;
func() = 20; //引用为返回值的函数可以做左值
cout << func() << endl;
return 0;
}
3. 函数重载
必须在一个作用域下。
void func(int a, char b) {cout << "111" << endl;}
void func(char a, int b) {cout << "444" << endl;}
void func(char a) {cout << "222" << endl;}
int func(char a) {cout << "333" << endl;return 0;}
int main() {
func(1, '1');
func('2'); //参数个数和类型不同的重载
func('2'); //无法重载仅按返回值类型区分的函数(避免二义性)
func('1', 1); //参数顺序不同的重载
return 0;
}
//注意事项
//函数默认参数使用需要注意重载。
4. 类和对象
类与结构体区别:不写权限的情况下,class默认private,struct默认public。
PS:
类的成员变量和成员函数是分开存储的,静态成员变量和静态/非静态成员函数都不属于类的对象上。
空对象占用1字节,是编译器为了区分空对象占内存的位置分配的。
4.1 封装
意义:将属性和方法统一为整体(成员),并加以权限控制。
访问权限:
class abc {
public:
//公共权限,成员类内外都可以访问
protected:
//保护权限,成员只有类内可以访问,允许子类继承
private:
//私有权限,成员只有类内可以访问,不允许子类继承
};
分包:对于大的工程来说,所有类写在一个作用域下不现实,一般拆成.h和.c两个文件。前者保存类所需的头文件,类的成员属性和方法的声明,后者保存方法的具体实现。
4.2 成员私有化
优点:
1.自己控制读写的权限。
2.对于写可以进行数据有效性检测。
class Person {
public:
void setname(string a) {
if (a.size() != 4) { cout << "re-input" << endl; return; }
name = a;
}
string getname(){ return name; }
int getage() { age = 12; return age; }
private:
string name; //可读可写--要求名字只能两个字
int age; //只读
};
int main() {
Person p;
p.setname("张三");
cout << p.getname() << endl;
cout << p.getage() << endl;
return 0;
}
4.3 对象特性–构造和析构
4.3.1 构造和析构:
构造函数:用于创建对象时为对象的成员属性赋值,权限为pubilc。
语法:类名(可以写参数,因此可以重载){}
析构函数:用于对象销毁前的清理工作,权限为public。
两者都由编译器自动调用。
语法:~类名(不可以有参数){}
顺序:析构先进后出,构造相反
4.3.2 构造函数分类及调用:
按参数分:有参/无参
按类型分:普通构造/拷贝构造
三种调用方式:括号法,显示法,隐式转换法。
class Person {
public:
//普通构造
Person() { age = 10; } //无参/默认
Person(int a) { age = a; } //有参
//拷贝构造--用已有成员初始化新成员
Person(const Person& p) { age = p.age; }
~Person() {}
int age;
};
void test() {
//1.括号法
Person p; //默认构造不要加(),否则编译器会认为是函数声明
Person p1(12);
Person p2(p);
cout << p.age << p1.age << p2.age << endl;
//2.显示法
Person q;
Person q1 = Person(12); //=右侧是匿名对象
Person q2 = Person(q); //匿名对象在当前行调用结束后系统会立刻析构
//Person (q2);-----这是错误的,用拷贝构造初始化匿名对象,编译器会忽视(),将其当作默认构造,导致对象重定义
cout << q.age << q1.age << q2.age << endl;
//3.隐式转换法
Person a = 10; //相当于Person a = Person(10);
Person b = a; //相当于Person b = Person(a);
}
4.3.3 拷贝构造使用时机:
void work(Person p) {}
Person work1() {
Person p(10);
return p; //这里拷贝构造了1个p作为返回值
}
void test() {
//1.用旧的对象初始化新的对象
Person p1(12);
Person p2(p1);
//2.值传递方式给函数参数传值
work(p1); //这里其实拷贝构造了一个临时p1传给work()
//3.值方式返回局部对象
Person p3 = work1();
cout << p1.age << p2.age << p3.age << endl;
}
4.3.4 深拷贝与浅拷贝:
浅拷贝:简单的赋值拷贝(可能导致堆区内存重复释放)
深拷贝:在堆区重新申请内存,进行拷贝操作
class Person {
public:
Person(int a, int b) {
age = a;
height = new int(b); //在堆区申请内存
}
//如果不自定义拷贝构造函数,编译器自动调用默认拷贝,只能进行浅拷贝
//浅拷贝导致p1和p2的height指向同一块内存。
//导致析构时p2先释放了height指向的内存,p1析构时就产生重复释放
//最终导致程序崩溃
Person(const Person& p) {
age = p.age;
height = new int(* p.height); //在堆区重新申请内存进行深拷贝
}
~Person() {
if (height != NULL) {
delete(height);
height = NULL;
}
}
int age;
int* height;
};
int main()
{
Person p1(23, 170);
Person p2(p1);
cout << *p1.height << *p2.height << endl;
return 0;
}
4.4 对象特性–初始化列表
用于初始化类属性。
语法:构造函数():属性(值)…{}
class Person {
public:
Person(int a, int b, int c):A(a), B(b), C(c){}
int A;
int B;
int C;
};
int main() {
Person p(11, 22, 33); //已经由初始化列表赋初值
cout << p.A << p.B << p.C << endl;
return 0;
}
main中调用看起来和有参构造一样,感觉没什么用,或许意义在于代码更简洁?
4.5 对象特性–静态成员
静态成员变量:
1.所有对象共享一份数据,也就是说不属于某一个特定的对象。
2.在编译阶段就分配全局区内存。
3.必须类内声明,类外初始化。
class Person {
public:
static int A;
};
int Person::A = 100;
int main() {
Person p1;
cout << p1.A << endl; //通过对象访问
Person p2;
p2.A = 200; //由于数据共享,这里p1.A也变为200了
cout << p1.A << endl;
cout << Person::A << endl; //通过类名访问
return 0;
}
静态成员方法:
1.所有对象共享一个静态成员函数。
2.只能访问静态成员变量(因为无法区分非静态成员变量属于哪一个对象)。
class Person {
public:
static void func() {
A = 111;
cout << A << endl;
}
static int A;
};
int Person::A = 100;
int main() {
Person p1;
p1.func(); //1.通过对象访问
Person::func(); //2.通过类名访问
return 0;
}
4.6 对象特性—this指针
非静态成员函数被调用时,可能有很多个对象都在调用,为了识别是哪一个对象调用,c++提供了this指针。
this指针是隐含在非静态成员函数中的一种指针,不需要定义可以直接使用,其指向为被调用的成员函数所属的对象。
用途:
1.形参和成员变量同名时的区分。
2.在类的非静态成员函数中返回对象本身。
本质:
this指针指向不可以修改,本质上是指针常量。
即class_name * const this;
class Person {
public:
Person(int A) {
this->A = A; //解决名称冲突
}
Person& A_add(Person &p){ //引用方式返回对象本身
this->A += p.A;
return *this; //this指向p2.*this就是p2本身
}
int A;
};
int main() {
Person p1(17);
Person p2(13);
p2.A_add(p1).A_add(p1); //链式编程--p2.A_add返回p2
cout << p2.A << endl;
return 0;
}
4.7 对象特性—空指针访问成员函数
原因:下列代码中func中调用了成员变量,其中A前面其实隐含了一个this->,这就导致this为NULL,访问了非法内存,从而导致调用失败。如果func不访问类的成员变量的话,就相当于一个类外函数,可以正常调用。所以很多程序猿会在成员函数里加一个判断。
class Person {
public:
void func() {
if (this == NULL) { return; }
cout << A << endl; //this->A
}
int A = 2;
};
int main() {
Person* p = NULL;
p->func(); //调用失败,VS最新的好像不会崩溃了
return 0;
}
4.8 对象特性—const修饰成员函数
常函数:
在成员函数后加const修饰,特性是内部不可以修改成员属性,如果还想修改,需要在成员属性声明的时候加mutable关键字。
常对象:
声明对象前加const,常对象只能调用常函数。
常函数原理:
前面说过了this指针的本质是指针常量,其指向不可修改,常函数其实是在this指针的基础上使其指向的值也禁止修改,也就是:const class_name * const this;
体现在函数的创建上就是:返回值类型 函数名() const{}
class Person {
public:
void func() const{
A += 1; //错误的,不理智的
B += 1; //正确的,可编译的
}
void func1(){}
int A = 2;
mutable int B;
};
int main() {
const Person p; //常对象
p.A = 100; //不可以修改A
p.B = 100; //可以修改B
p.func1(); //不可以调用非常函数
return 0;
}
4.9 友元
作用:让一个函数或类可以访问另一个类中的私有成员。
三种类型:
1.全局函数友元
2.类友元
3.成员函数友元
class Person;
class woman {
public:
woman();
void visit();
Person* w;
};
class Person {
friend void gay(Person* p); //将全局函数列为友元
friend class dog; //将类列为友元
friend void woman::visit(); //成员函数列为友元
public:
Person() { A = "让我康康"; }
private:
string A;
};
//为什么一定要放到类外,而且要在Person类后面?
woman::woman() { w = new Person; }
void woman::visit() { cout << w->A << endl; }
class dog {
public:
void liugou() { cout << p->A << endl; }
Person* p = new Person;
};
void gay(Person* a) {
cout << a->A << endl;
}
int main() {
Person p1;
gay(&p1);
dog dog1;
dog1.liugou();
woman p2;
p2.visit();
return 0;
}
关于注释中的问题:
首先要了解前向声明。可以声明一个类而不定义它,这个声明被称为前向声明,如代码块中的class Person;。
这个时候,即声明之后,定义之前,Person类还是一个不完全类型,只能以有限方式使用,禁止定义该类型对象,只能用于定义指向该类型的指针/引用,或用于声明该类型作为形参类型或者返回值的函数。
因此,需要把woman类中涉及构造Person对象的操作放到类外,并且在Person类定义之后。
建议:干脆把所有类的构造和成员函数都写在类外,统一放到前向声明,类定义后面。
4.10 运算符重载
PS:
1.内置类型运算符无法改变,比如正常+
2.不要滥用运算符重载。
//通过全局函数重载了+
Person operator+ (Person& p1, Person& p2) {
Person temp;
temp.B = p1.B + p2.B;
return temp;
}
//调用
Person a, b, c;
a.B = 10; b.B = 20;
c = a + b; //这里+是重载后的+,实现了不同对象成员属性的加法
//用类内成员函数重载也是一样
//operator<<重载左移运算符(一般用全局函数)
ostream& operator<<(ostream& cout, Person& p) {
cout << p.B << endl;
return cout;
}
cout << c.B << endl; //调用默认的<<
cout << c << endl; //调用重载<<,为了连续使用<<需要链式编程
//重载赋值运算符operator=
//作用是避免默认=带来的浅拷贝析构崩溃问题
//仿函数-函数调用运算符()重载
//类内重载
int operator()(int a, int b) {
return a + b;
}
//类外调用
Person a;
cout << a(10, 10) << endl;
cout << Person()(1, 2) << endl; //匿名函数对象Person()