C++基础学习
一、C++的引用知识点
1.1 c++中的引用
通过引用来创建一个别名,通过别名去改变需要改变的变量的值
语法:数据类型 &别名 = 需要改变的变量
就是通过获取到别名的地址,完成同步修改
演示:
int a = 10;
int* b = a;//运用引用知识
cout<<"a = "<< a <<endl;//10
cout<<"b = "<< b <<endl;//10
//通过b来改变a的值,两个的值同时改变
b = 100;
cout<<"a = "<< a <<endl;//100
cout<<"b = "<< b <<endl;//100
注意
:
-
在进行引用的时候要与源数据类型一致
-
在进行引用的时候必须初始化
int &b;
(这是错误的写法)int a = 10; int* b = a;
这才对 -
引用初始化过后就不能改变了
int a = 10;
int* b = a;
int c = 100;
b= c //这里不是引用而是赋值了
cout<<"a = "<< a <<endl;//100
cout<<"b = "<< b <<endl;//100
cout<<"c = "<< c <<endl;//100
1.2 引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参
优点: 可以简化指针修改实参
即:与传址的效果一致,但是相对于传址更加方便
演示:
//传值
void swap1(int a, int b){
int temp = a;
a=b;
b=temp;
}
//传址
void swap2(int* a , int* b){
int temp = *a;
*a = *b ;
*b = temp;
}
//传引用
void swap3(int &a , int &b){
int temp = a;
a=b;
b=temp;
}
1.3 引用做函数的返回值
作用: 引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法: 函数调用作为左值
#include<stdio.h>
#include<iostream>
using namespace std;
int& str1() {
int a = 10;//这是局部变量,离开这就释放了资源
return a;
}
int& str2() {
static int a = 10;//这里表示是静态变量,随着程序的消失而释放资源
return a;
}
int main() {
int &c = str1();
cout << "c= " << c << endl;//获取到的是随机数
int &d = str2();
cout << "d= " << d << endl;//获取到的是10,因为这里的d引用的为a的值
}
1.4 引用的本质
本质:引用的本质在c++内部实现是一个指针常量
即:int* const ref = &a;
#include<stdio.h>
#include<iostream>
using namespace std;
//引用的实质是:int* const ref = &a;
void fun(int& ref) {
ref = 100; //ref为引用,转化为 *ref = 100;
}
int main(){
int a = 10;
int& ref = a;//系统自动转化为 int * const ref = &a;由于是const类型所以不能改变引用对象
ref = 20; //自动识别到为引用,即 *ref = 20;
cout << "ref = " << ref << endl;//20
cout << "a = " << a << endl;//20
fun(a);
cout << "ref = " << ref << endl;//100
cout << "a = " << a << endl;//100
}
结论:
C++推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了
1.5 常量引用
作用:
常量引用主要用来修饰形参,防止误操作
在函数形参列表中,可以加const修饰形参防止形参改变实参
#include<stdio.h>
#include<iostream>
using namespace std;
//创建一个打印函数,但是不能改变其原数字
void myprint(const int &val) {
//val = 100; ----> 报错
cout << "val = " << val << endl;
}
int main() {
//int &a = 10; -->报错,并没有创建合法的内存空间
const int& a = 10;//正确,
//因为在这块内部自动转化为 int temp = 10 ; const int &a = temp;
myprint(a);//在函数体内只能读,不能修改,因为传入的参数为常量引用
}
二、函数进阶
2.1 函数的默认参数
在C++中,函数的形参列表中的形参是可以有默认值的。
语法:返回值类型函数名(参 默认值)
- 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
// void sum(int a = 10 ,int b ,int c) ---> 错误
// void sum(int a = 10 ,int b=10 ,int c) ---> 错误
void sum (int a , int b ,int c=10 ,int d = 10); //正确
void sum (int a , int b ,int c ,int d = 10); //正确
- 如果函数声明有默认值,函数实现的时候就不能有默认参数
正确写法:
void sum(int a=10,int b=10);
void sum (int a , int b){
return a + b;
}
//或者
void sum(int a,int b);
void sum (int a=10 , int b=10){
return a + b;
}
错误写法:
```cpp
void sum(int a=10,int b=10);
void sum (int a=20 , int b=20){
return a + b;
}
//或者
void sum(int a,int b=20);
void sum (int a , int b=10){
return a + b;
}
这样会产生二义性
2.2 函数占位参数
C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
语法:返回值类型 函数名 (数据类型)
void fun (int a , int){
cout<<"asdas"<<endl;
}
int main(){
fun(10,10);//这里必须得传入第二个参数进行占位
}
2.3 函数重载
作用:
函数名可以相同,提高复用性
- 函数重载满足条件:
。同一个作用域下
。函数名称相同
。函数参数类型不同 或者 个数不同 或者 顺序不同
注意:
函数的
返回值不可以
作为函数重载的条件
演示如下:
#include<stdio.h>
#include<iostream>
using namespace std;
void fun() {
cout << "这是一个空参函数的调用" << endl;
}
void fun(int a,double b) {
cout << "这是带有两个参数的函数调用" << endl;
}
void fun(double a, int b) {
cout << "函数重载函数的调换位置" << endl;
}
int main() {
fun();
fun(10, 2.3);
fun(2.3, 10);
return 0;
}
重载的注意事项
-
- 可以利用引用重载函数
但是加const 和 不加 const 是两个不同类型的重载函数,再不出错误的形势下执行不加const的重载函数
- 可以利用引用重载函数
演示:
#include<stdio.h>
#include<iostream>
using namespace std;
void func1(int& a) {
cout << "调用func1(int& a)函数" << endl ;
}
void func1(const int& a) {
cout << "调用func1(const int& a)函数" << endl;
}
int main() {
func1(10);//func1(const int& a) 函数
int a = 10;
func1(a);//func1(int& a) 函数
return 0;
}
-
- 重载函数的默认参数问题
#include<stdio.h>
#include<iostream>
using namespace std;
void func1(int a, int b = 10) {
cout << "调用含有默认的参数" << endl;
}
void func1(int a) {
cout << "调用无默认值的函数" << endl;
}
int main() {
//func1(1); 报错 , 出现二义性
func1(10, 23);
}
三、面向对象
C++面向对象的三大特性为:封装、继承、多态
C++认为万事万物都皆为对象对象上有其属性和行为
3.1 封装
3.1.1 封装的意义
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
语法: class 类名 {访问权限 : 属性 / 行为};
#include<stdio.h>
#include<iostream>
using namespace std;
const double pi = 3.14;
//定义一个圆对象
class Circles {
public:
//属性,定义其半径
int R;
//行为:计算圆周长
double zhouchang() {
return 2 * pi * R;
}
};
int main() {
//创建对象(实列化)
Circles c1;
c1.R = 10;
cout << "该圆的周长为:" << c1.zhouchang() << endl;
}
3.2 封装的访问权限
- public公共权限
全部都可以利用
- protected 保护权限
所有的子类和继承类可以使用
类外不能使用 - private私有权限
只有当前文件下能够使用
类外不能使用
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
//封装学生对象
class Student {
protected:
//学生姓名
string name;
private:
//学生id
int ID;
public:
void Setname(string names) {
name = names;
}
void SetId(int id) {
ID = id;
}
void showifo() {
cout << "学生姓名:" << name << endl;
cout << "学生ID:" << ID << endl;
}
};
int main() {
//创建学生对象
Student s1;
// s1.name = "阿里嘎多"; 报错,他是受保护权限,不能在类外使用
//s1.id = 13; 报错,他是受私有权限,不能在类外使用
s1.Setname("阿里嘎多");
s1.SetId(15);
s1.showifo();
return 0;
}
3.3 struct和class区别
- struct 默认权限为
公共
- class默认权限为
私有
3.4 构造函数与析构函数
- 构造函数:
主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调。
- 析构函数:
主要作用在于对象销毁前系统自动调用,执行一些清理工作.
3.4.1 构造函数
构造函数语法: 类名(){}
调用时注意事项:
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
3.4.2 析构函数
析构函数语法: ~类名(){}
析构函数的注意事项:
- 析构函数,没有返回值也不写void
- 函数名称与类名相同,在名称前加上符号
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
#include<stdio.h>
#include<iostream>
using namespace std;
class person {
public:
//自动调用,可有参数,可重载
person() {
cout << "调用构造方法" << endl;
}
//自动调用,无参数,不可重载
~person() {
cout << "调用析构函数" << endl;
}
};
void test() {
person p1;
}
int main() {
test();
return 0;
}
3.4.3 构造函数的分类及调用
两种分类方式
按参数分为:
- 有参构造
- 无参构造
//无参构造
Person() {
cout << "创建无参构造函数" << endl;
}
//有参构造
Person(int a ) {
cout << "创建有参构造函数" << endl;
age = a;
}
按类型分为:
- 普通构造
- 拷贝构造
//拷贝构造
Person(const Person& p) {
age = p.age;
cout << "调用拷贝函数" << endl;
}
三种调用方式:
- 括号法
//括号法
Person p1;//无参调用(默认调用)
Person p2(10);//有参调用
Person p3(p2);//拷贝构造
在调用无参构造方法的时候,不用打后面的括号 --> Person p;
若为Person p1()
程序会认为是函数的声明
- 显示法
//显示法
Person p4 = Person();//空参构造
Person p5 = Person(30);//有参构造
Person p6 = Person(p5);//拷贝构造
//Person(10)单独写就是匿名对象 当前行结束之后,马上析构
- 隐式转换法
Person p4 = 10;
Person p5 = Person(p4);
// Person p6 = p5; ---> 报错
注意2: 不能利用 拷贝构造数 初始化名对象 编译器认为是对象声明
3.5 拷贝构造函数的调用时机
- 使用一个已经创建完毕的对象来初始化一个新对象
void test1() {
Person p1(10);
Person p2(p1);
}
int main() {
test1();
}
- 值传递的方式给函数参数传值
void dowork(Person p) {
}
void test1() {
Person p1;
dowork(p1);
}
int main() {
test1();
}
- 以值方式返回局部对象
Person dowork() {
Person p1;
return p1;
}
void test1() {
Person p1 = dowork();
}
int main() {
test1();
}
3.6 构造函数的调用规则
默认情况下,c++编译器至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下
- 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
3.7 深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新电请空间,进行拷贝操作
#include<stdio.h>
#include<iostream>
using namespace std;
//创建Person对象
class Person {
public:
int age;//年龄
int* height;//身高
//空参构造函数
Person() {
cout << "调用Person的空参构造函数" << endl;
}
//有参构造函数
Person(int age1 , int height1) {
age = age1;
height = new int(height1);
cout << "调用Person的有参构造函数" << endl;
}
//创建深拷贝函数
Person(const Person& p) {
age = p.age;
height = new int(*p.height);
cout << "调用Person的拷贝函数" << endl;
}
//创建析构函数
~Person() {
//如果不利用深堵贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
if (height != NULL) {
delete height;
height = NULL;
}
cout << "调用Person的析构函数" << endl;
}
};
void test() {
Person p1 = Person(18, 180);
cout << "p1身高" << *p1.height << endl;
Person p2 = Person(p1);
cout << "p2身高" << *p2.height << endl;
}
int main() {
test();
}
总结: 如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
3.8 初始化列表
作用:
C++提供了初始化列表语法,用来初始化属性
语法: 构造函数(): 属性1(值1),属性2 (值2){}
//空参构造函数,初始化列表
Person(int a):age(a) {
cout << "调用Person的空参构造函数" << endl;
}
3.9 静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
#include<stdio.h>
#include<iostream>
using namespace std;
//定义一个Person类
class Person {
public:
//创建一个权限为public的静态成员变量
static int age;
private:
//创建一个权限为私有的静态成员变量
static int height;
};
//在类内声明,类外初始化
int Person::age = 10;
int Person::height = 180;
void test() {
Person p;
//访问方法:
//法一:创建对象来调用
cout << "这个人的年龄:" << p.age << endl;
//cout << "这个人的身高:" << p.height << endl; ----> 报错,因为含有访问权限private
//法二:利用预解析来访问
cout << "这个人的年龄:" << Person::age << endl;
//cout << "这个人的身高:" << Person::height << endl; ----> 报错,因为含有访问权限private
}
int main() {
test();
}
静态成员函数
- 所有对象共享同一个函数
静态成员函数
只能访问静态成员变量
#include<stdio.h>
#include<iostream>
using namespace std;
//创建Person类
class Person {
public:
static int age;
int height;
static void func1() {
cout << "调用func1构造函数" << endl;
age = 10;
//height = 100; --> 报错,因为在静态方法当中只能用静态成员变量
}
private:
static void func2() {
cout << "调用func2构造函数" << endl;
age = 10;
//height = 100; --> 报错,因为在静态方法当中只能用静态成员变量
}
};
int Person::age = 200l;
void test() {
Person p;
//法一:通过对象调用法
p.func1();
//法二:通过类名调用法
Person::func2();
}
int main() {
test();
}
四、C++对象模型和this指针
4.1 成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储只有非静态成员变量才属于类的对象上
int m_A; //非静态成员变量 属于类的对象上
static int m_B;//静态成员变量 不属于类对象上
void func(); //非静态成员函数 不属于类对象上
static void func(){} //静态成员函数 不属于类的对象上
空对象占用内存空间为: C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置每个空对象也应该有一个独一无二的内存地址
4.2 空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
#include<stdio.h>
#include<iostream>
using namespace std;
class Person {
public:
int age;
Person(int age) {
//这里就表示的不同的age值,防止出现赋值同名的现象
this->age = age;
}
void showifo() {
cout << "成员函数调用" << endl;
}
void showtimes() {
cout << "年龄为:" << age << endl;
}
};
void test() {
//设置为空指针
Person* p = NULL;
p->showifo();
//p->showtimes(); 报错,这里是空指针,在使用的时候空对象没有age值对应
}
int main() {
test();
}
4.3 const修饰成员函数
常函数:
- 成员函数后加const后我们称为这个函数为常函数.
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
- 声明对象前加const称该对象为常对象.
- 常对象只能调用常函数
#include<stdio.h>
#include<iostream>
using namespace std;
class Person {
public:
int age;
mutable int height;
//常函数不能修改属性,但是mutable修饰过的可以
//含义:const Person const *p,加在后面的const修饰的成员函数表示让this对应的值不能修改
void showfo() const {
height = 10;
}
void ifcon() {
}
};
void test() {
//常对象只能调用常函数
Person const p;
p.showfo();
//p.ifcon(); //报错
}
4.4 友元函数
在程序里,有些私有属性 也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的目的就是让一个函数或者类 访问另一个类中私有成员
友元的关键字为 friend
友元的三种实现
- 全局函数做友元
- 类做友元
- 成员函数做友元
4.4.1 全局函数做友元
让全局函数去访问类当中的私有成员变量
friend void show(Person* p);
:全局是Person类的好朋友可以访问私有变量
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
class Person {
friend void show(Person* p);
private:
string seting;
public:
string bedroom;
Person() {
seting = "客厅";
bedroom = "卧室";
}
};
//创建一个全局函数
void show(Person *p) {
cout << "访问的地方:" << p->bedroom << endl;
cout << "访问的地方:" << p->seting<< endl;
}
int main() {
Person p;
show(&p);
}
4.4.2 类做友元函数
作用:
让其他类也可以访问到当前类下的私有成员属性
#include<stdio.h>
#include<iostream>
#include<string>
using namespace std;
class Building;
//类中的友元函数
class GoodFriend {
public:
void visit();
GoodFriend();
Building* building;
};
class Building {
friend class GoodFriend;
public:
string sitting;
Building();
private:
string bedroom;
};
Building::Building() {
this->bedroom = "卧室";
this->sitting = "客厅";
}
GoodFriend::GoodFriend() {
building = new Building;
}
void GoodFriend::visit() {
cout << "好基友正在访问" << building->sitting<< endl;
cout << "好基友正在访问" << building->bedroom<< endl;
}
void test() {
GoodFriend gg;
gg.visit();
}
int main() {
test();
}
4.4.3 让成员函数做友元函数
作用:
就是然后类外的成员函数来访问当前类当中的私有变量
#include<stdio.h>
#include<iostream>
#include<string>
using namespace std;
class Building;
class goodgad {
public:
Building* building;
void visit();
goodgad();
};
class Building {
friend void goodgad::visit();
public:
string sitting;
Building() {
bedroom = "卧室";
sitting = "客厅";
}
private:
string bedroom;
};
goodgad::goodgad() {
building = new Building;
}
void goodgad::visit() {
cout << "好基友正在访问:" << building->bedroom << endl;
cout << "好基友正在访问:" << building->sitting<< endl;
}
void test() {
goodgad gg;
gg.visit();
}
int main() {
test();
}