(C++基础随笔) 02 c++

C++的内存分区

代码区、全局区、栈区、堆区

代码区

存放二进制程序未执行前,分成两个区域:代码区、全局区

共享的:复用

只读的:防止意外更改

全局区

存放全局变量、静态变量、常量、const修饰的全局变量(全局常量)

这个区域的数据在程序结束后,由操作系统释放

栈区

程序运行后,此区域数据由操作系统管理

不要返回局部变量的地址,因为其由编译器自动释放

int* func() {
	int a = 10;
	return &a;
}

int main() {
	int* p = func();

	cout << *p << endl;//第一次编译器会保留这个数据
	cout << *p << endl;//第二次就会出错
	return 0;
}

堆区

程序员管理生存周期,程序结束系统自动回收

new就是在堆区开辟内存

new返回的是堆区变量的地址

new int[10]表示需要在堆区创建一个长度10的数组

int* func() {
	//用new在堆区开辟内存
	int *p=new int(10);
	return p;//这里的指针p实际上也是一个局部变量,放在栈区。但指针指向的数据int(10)是放在堆区的
}

int main() {
	int* p = func();

	cout << *p << endl;
	cout << *p << endl;

	return 0;
}

释放堆区的变量是delete,释放数组的时候要用delete[]

C++引用

作用:给变量起别名

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

引用的注意事项

1、引用必须初始化

int &b;//这个写法是错误的

2、引用初始化后就不能变了

int a=10;
int c=100;
int &b=a;
int &b=c;//不允许

引用做函数参数

值传递(使用引用可以让形参啊修饰实参,简化指针)

地址传递

引用传递

//值传递
void swap1(int a,int b) {
	int tmp;
	tmp = a;
	a = b;
	b = tmp;
	cout << "swap1  a: " << a << "     swap1    b:  " << b << endl;
	return;
}
//地址传递
void swap2(int *a,int *b) {
	int tmp;
	tmp = *a;
	*a = *b;
	*b = tmp;
	cout << "swap2  a: " << *a << "     swap2    b:  " << *b << endl;
	return;
}
//引用传递
void swap3(int &a,int &b) {
	int tmp;
	tmp = a;
	a = b;
	b = tmp;
	cout << "swap3  a: " << a << "    swap3    b:  " << b << endl;
	return;
}


int main() {
	int a = 1;
	int b = 100;
	swap1(a, b);
	swap2(&a, &b);
	swap3(a, b);
	return 0;
}

引用做函数返回值

注意:不要返回局部变量引用

函数的调用可以作为左值

int & test01() {
	int a = 10;
	return a;//不能返回局部变量的引用
}
int& test02() {
	static int a = 10;
	return a;//不能返回局部变量的引用
}


int main() {
	//int& ref = test01();
	//cout << "ref:  " << ref << endl;//ref:  -858993460
	int& ref = test02();
	cout << "ref:  " << ref << endl;//ref:  10

	//函数调用可以作为左值
	test02() = 1000;
	cout << "ref:  " << ref << endl;//ref:  1000
	return 0;
}

引用的本质

本质:引用的本质在C++内部的实现是一个指针常量(int * const ref=&a)

//当作为函数参数时,内部发现是引用,转换为int * const ref=&a
void func(int& ref) {
	ref = 100;
}

int main() {
	int a = 10;

	//自动转换为int * const ref = &a;指针常量是指针指向不可变,指向的值可以变,这就说明了为什么别名初始化之后就不能更改了
	int& ref = a;
	ref = 20;//内部发现ref是引用,自动转换为*ref=20
	cout << "a:  " << a << endl;
	cout << "ref:  " << ref << endl;

	func(a);
	return 0;
}

常量引用

作用:常量引用主要用于修饰形参,防止误操作,防止形参改变实参

void showValue(const int& v) {
	//v += 10;//只要用const限定了引用,这里对引用的修改就会报错
	cout << v << endl;
}

int main() {
	int a = 10;
	int& ref = a;
	showValue(ref);
	return 0;
}

C++函数高级

默认参数

int func(int a, int b = 10, int c = 20) {
	return a + b + c;
}
int main() {
	int result=func(30);
	cout << result << endl;
	return 0;
}

注意事项

1、如果一个位置已经有了默认参数,那么这个位置之后必须设置默认参数

2、如果函数声明有默认参数,那么它的实现不能包含默认参数

函数占位参数

注意:调用函数时必须填充这个位置!

int func(int, int b = 10, int c = 20) {
	return b + c;
}
int main() {
	int result=func(100);
	cout << result << endl;
	return 0;
}

目前阶段,这个占位的int参数还用不上

函数重载

满足条件

1、同一作用域下

2、函数名称相同

3、函数参数类型不同、个数不同或顺序不同

注意

返回值不同不能作为重载的条件

void print(int a) {
	cout << a << endl;
	return;
}
void print(int a,int b) {
	cout << a <<"  " << b << endl;
	return;
}

int main() {
	print(1);
	print(1, 2);
	return 0;
}

函数重载的注意事项

引用作为重载条件

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

int main() {
	int a = 10;
	func(a);//func(&a)调用
	func(100);//const int& a调用
	//因为int &a=100不合法,引用的对象需要是一个堆区或者栈区的数据,而100是个常量,放在全局区所以不合法;
	//而const int &a=100,常量=常量,语法正确所以只能调用这个
	return 0;
}

函数重载碰上默认参数

void func2(int a,int b=20) {
	cout << "func2(int a,int b=20)调用" << endl;
}

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

int main() {
	//func2(10);//此时这种调用,两个重载的函数都能进去,出现二义性,会报错
	return 0;
}

类和对象!

三大特性:封装、继承、多态

C++中,万事万物都是对象

封装

封装的意义:把属性和行为放在不同的权限下加以控制

class Stu {
private:
	int sid;
	string sname;
	int sage;
public:
	void set_sid(int sid) {
		this->sid = sid;
		return;
	}
	void set_name(string sname) {
		this->sname = sname;
		return;
	}
	void set_sage(int sage) {
		this->sage = sage;
		return;
	}
	int get_sid() {
		return this->sid;
	}
	string get_sname() {
		return this->sname;
	}
	int get_sage() {
		return this->sage;
	}
	void printInfo() {
		cout << "sid:  " << get_sid() << "  " << "sname:  " << get_sname() << "  " << "sage:  " << get_sage() << endl;
	}
};


int main() {
	Stu newstu;
	newstu.set_sid(1);
	newstu.set_name("alex");
	newstu.set_sage(21);
	newstu.printInfo();

	system("pause");
	return 0;
}

public 类内外都可以访问

private 只有类内可以访问

protect 类外不可以访问,儿子可以访问父类

struct和class的区别

struct默认访问权限为public

class默认访问权限为private

将成员属性设置为私有private

优点

1、可以自己控制读写权限

2、对于写权限,可以检测数据的有效性

构造函数和析构函数

初始化和清理是一个安全问题,编译器自动提供并调用

构造函数:类名(){}
1、没有返回值也不写void
2、函数名和类名相同
3、可以有参数,因此可以重载
4、自动调用且仅调用一次


析构函数:~类名(){}
1、没有返回值也不写void
2、函数名和类名相同,加~
3、不可以带参数
4、自动调用且仅调用一次
class Stu {
public:
	Stu() {
		cout << "构造函数被调用  "<< endl;
	}
	Stu(string s) {
		cout << "构造函数被调用  s:  " << s << endl;
	}

	~Stu() {
		cout << "析构函数被调用  " << endl;
	}
	
};


int main() {
	Stu newstu;
	Stu newstu2("alex");//带参数的构造函数是这样初始化的!

	system("pause");
	return 0;
}

构造函数的分类及调用

两种分类

按参数:有参构造和无参构造

Stu() {
    cout << "构造函数被调用  "<< endl;
}
Stu(string s) {
    cout << "构造函数被调用  s:  " << s << endl;
}

按类型:普通构造和拷贝构造

class Stu {
private:
	int sid;
	string sname;
public:
	Stu() {
		cout << "构造函数被调用  "<< endl;
	}
    Stu(string s) {
    cout << "构造函数被调用  s:  " << s << endl;
	}

	//拷贝构造函数
	Stu(const Stu(&stu)) {//const是为了防止误操作修改了本体的值,拷贝构造包含了本体所有的属性和方法
		sname = stu.sname;
	}
	void set_sname(string sname) {
		this->sname = sname;
	}
	void get_sname() {
		cout<<this->sname << endl;
	}
};


int main() {
	Stu s1;//默认构造函数的调用
	s1.set_sname("alex");
	Stu s2(s1);//拷贝构造函数的调用
	s2.get_sname();
	system("pause");
	return 0;
}

三种调用方式

括号法

Stu s1;//默认构造函数的调用
s1.set_sname("alex");
Stu s2(s1);//拷贝构造函数的调用

使用默认构造函数时,不要写成

Stu s1();//编译器会把这个当成一个函数的声明

显示法

Stu s1;
Stu s2 = Stu(s1);//多了一步显式的赋值
Stu;//使用无参的构造函数生成的一个匿名对象,这一行结束就会析构
s1.set_sname("alex");
s2.get_sname();
注意

不要用拷贝构造函数来初始化一个匿名对象

Stu s1;
Stu(s1);//这个匿名对象无法被创建,因为编译器自动转换为Stu s1;与上面重定义

隐式转换法

Stu s2="alex"//等价于Stu s2=Stu("alex")
Stu s3=s2//拷贝构造,等价于Stu s3=Stu(s2)

拷贝构造函数的调用时机

1、使用一个已经创建完毕的对象来初始化一个新的对象

Person p1(21);
Person p2(p1);

2、值传递的方式给函数参数传值

void dosomething(Person p) {//此时会调用拷贝构造函数来生成这个形参
	
}

void test1() {
	Person p;
	dosomething(p);
}


int main() {
	test1();
	system("pause");
	return 0;
}

3、以值方式返回局部对象

Person dosomething2() {
	Person p;
	return p;//因为是以值的方式返回的,所以这里返回的时候会创建一个新对象,构造函数用的就是拷贝构造函数
}

void test2() {
	Person p = dosomething2();
}


int main() {
	test2();
	system("pause");
	return 0;
}

构造函数的调用规则

默认情况

默认情况下C++编译器至少给一个类添加了三个函数:

默认构造函数、默认析构函数、默认拷贝构造函数

调用规则

用户提供有参构造函数,编译器就不提供默认构造函数了,但仍然会提供默认拷贝构造函数

用户提供了拷贝构造函数,编译器就不会提供默认拷贝构造函数

深拷贝和浅拷贝

面试题常出

浅拷贝

简单的赋值拷贝操作

浅拷贝时,堆区的空间会重复释放

class Person{
public:
	Person() {
		cout << "无参构造函数调用" << endl;
	}
	Person(int age,int height) {
		this->mage = age;
		this->mHeight = new int(height);//开辟在堆区,堆区的数据需要在析构时释放
		cout << "有参构造函数调用" << endl;
	}
	//Person(const Person & p) {
	//	cout << "拷贝构造函数调用" << endl;
	//}
	~Person() {
		cout << "析构函数调用" << endl;
		if (mHeight != NULL) {
			delete mHeight;
			mHeight = NULL;//为了防止野指针出现
			cout << "mHeight已释放" << endl;
		}
	}
	void set_age(int age) {
		this->mage = age;
	}
	int get_age() {
		cout << "age:   " << this->mage << endl;
		cout << "height:   " << *this->mHeight << endl;
		return this->mage;
	}

private:
	int mage;
	int* mHeight;//为了把数据开辟在堆区

};

void test01() {
	Person p1(18,180);
	//如果使用编译器提供的拷贝构造函数,会执行浅拷贝,逐字节拷贝
	//因为在栈内,先进后出,p2先被析构,要释放此时堆区的mHeight
	//当p1被析构时,也会去释放堆区的mHeight,导致堆区重复释放的异常
	Person p2(p1);
	p1.get_age();
	p2.get_age();

}

int main() {
	test01();
	system("pause");
	return 0;
}

深拷贝

在堆区重新申请空间,进行拷贝

堆区重复释放的解决方法,自定义深拷贝构造函数

//自定义深拷贝构造函数
Person(const Person & p) {
    cout << "深拷贝构造函数调用" << endl;
    mage = p.mage;
    mHeight = new int(*p.mHeight);//在执行拷贝构造时,单独在堆区开辟一片空间
}

初始化列表

作用:c++提供用来初始化属性

特别注意这个冒号的位置,在小括号后面!

//初始化列表赋初值
Person(int id,string name,int age,int height):mid(id),mname(name),mage(age),mHeight(height) {
}

要注意的是,这个构造函数也是重载的

类对象作为类成员

一个类的对象可以是另一个类

class Phone {
public:
	Phone() {
		cout << "Phone无参构造函数调用" << endl;
	}

	~Phone() {
		cout << "Phone析构函数调用" << endl;
	}
};

class Person{
public:
	Person() {
		cout << "Person无参构造函数调用" << endl;
	}

	~Person() {
		cout << "Person析构函数调用" << endl;
	}
	string pname;
	Phone pphone;
};

void test01() {
	Person p1;
}


int main() {
	test01();
	system("pause");
	return 0;
}

输出:

Phone无参构造函数调用
Person无参构造函数调用
Person析构函数调用
Phone析构函数调用

构造时先构造对象,析构时相反

静态成员

静态成员变量

所有对象共享

编译阶段分配内存

类内声明,类外初始化

静态成员函数

所有对象共享

静态成员函数只能访问静态变量

class Person{
public:
	Person() {
		cout << "Person无参构造函数调用" << endl;
	}

	~Person() {
		cout << "Person析构函数调用" << endl;
	}

	static void print() {
		//pname = "peter"; 
		cout << "hello " << endl;
		cout << secret << endl;
	}


	static string secret;
	string pname;
};

//静态成员变量很特殊,类内声明,类外初始化
string Person::secret = "Hello world!";

void test01() {
	Person p1;
	//两种访问方式
	//1、通过对象
	p1.print();
	//2、通过类名
	Person::print();
}

静态成员函数不能访问非静态成员变量,因为静态成员在内存中只有一个副本,而非静态成员在每一个对象中都有一个副本,所以这个静态方法无法区分要修改哪一个非静态变量

静态成员也可以指定private等访问权限

C++对象模型和this指针

成员变量和成员函数分开储存

在C++中,类内的成员变量和成员函数分开储存

只有非静态成员变量才属于类的对象

class Person{
	float a;
	static int b;
	void func() {
	
	}
	static void func2() {
	
	}
};

void test01() {
	Person p1;
	cout << sizeof(p1) << endl;
}

空对象占用1字节,因为C++编译器会给每个空对象也分配1字节

当添加了一个int类型的属性时,新对象占4字节

再添加一个静态 int b,新对象仍占4字节

再添加一个void方法,仍然占4字节

this指针

this指针指向被调用的成员函数所属的对象

this不用定义

作用:

1、解决名称冲突(如果构造函数使用的形参和成员变量重名,编译器认不出来)

class Person {
	Person(int age) {
		age = age;
	}
	int age;
};

2、返回对象本身用*this

以上例为例

class Person{
public:
	float a;
	static int b;
	void func() {
		cout << sizeof(this) << endl;
	}
	static void func2() {
	
	}
};
void test01() {
	Person p1;
	cout << sizeof(p1) << endl;
	p1.func();
	
}

this指针占8字节,解指针后,*this(就是p1)占4字节(也就是当前成员函数被调用的p1的大小)

class Person{
public:
	Person(int age) {
		this->age = age;
	}
    //因为返回的是一个特定的Person对象,所以要以引用的方式来接返回值
	Person& PersonAddAge(Person &p) {
		this->age += p.age;
		return *this;//这里*this解指针返回的是一个Person对象
	}
	int age;
};

void test01() {
	Person p1(10);
	Person p2(10);
	p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
	cout << p2.age << endl;
	
}
Person PersonAddAge(Person &p) {

当这里返回值类型是Person时,表示返回一个值类型的数据,就会调用Person的拷贝构造函数,创建一个新的对象,就不是原来的p2了

空指针访问成员函数

C++允许空指针访问成员函数,但是要注意函数体中有没有用到this指针

class Person{
public:
	Person(int age) {
		m_age = age;
	}
	void showClassName() {
		cout << "this is person class" << endl;

	}
	void showPersonAge() {
		cout << "person age:  " << m_age << endl;
	}
	int m_age;
};

void test01() {
	//Person p1(10);
	//p1.showClassName();
	//p1.showPersonAge();

	Person* p = NULL;//初始化空指针
	p->showClassName();//调用这个函数没有问题
	p->showPersonAge();//调用这个函数会崩溃,因为函数体中使用了m_age,默认会加上this->m_age
	
}

const修饰成员函数

常函数

  • 成员函数后加const后称为常函数

  • 常函数内部不可以修改成员属性

  • 成员属性声明时加了mutable后,在常函数中可以修改

常对象

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数
  • 成员属性声明时加了mutable后,在常对象中可以修改
class Person{
public:
	Person(int id,int age) {
		m_age = age;
		m_id = id;
	}

	//this指针的本质是    指针常量    指针的指向是不可以修改的
	//this = NULL; //这里会报错,this指针不可以修改指向,但是this指针指向的值可以修改
	void func() const //加上这个const,就是const Person* const this,表示this的指向和指向的值都不能修改
	{
		//m_age = 100;//加上const,常函数就不能修改成员变量了
		m_id = 1;//加上mutable的成员变量是可以在常函数中修改的
	}
	void sayHello() {
		cout << "hello" << endl;
	}

	int m_age;
	mutable int m_id;
};

void test01() {
	//非常对象,常函数和非常函数都能调用
	Person p1(1,20);
	p1.func();
	p1.sayHello();

	//常对象,只能调用常函数
	const Person p2(2,21);
	p2.func();
	//p2.sayHello();//语法会报错
	
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值