正在学习,未完待续······
文章目录
内存的本质结合《利用汇编挖掘编程语言本质第六节》课程理解
变量名规范参考:
全局变量:g_
成员变量:m_
静态变量:s_
常量:c_
函数重载
- 允许存在多个同名函数,但函数的参数必须不同(可以参数类型不同,也可以参数数量不同,也可以顺序不同),如果两个函数只有返回值不同也是不行的
int func(int a);
double func(int a);//会产生二义性
- C++对于重载函数的操作实际上会在编译时进行name mangling,将同名函数的名称稍加修改,而C没有name mangling机制所以不允许函数重载
默认参数
1.默认参数必须按照从右往左的顺序设置
void sum(int a=4, int b)//invalid
void sum(int a, int b=5)//valid
void sum(int a=4, int b=5)
{
cout << a+b;
}
int main ()
{
sum();
sum(10);//将10传给a,若只传一个值,默认按照从左往右的顺序传
sum(7,8);
}
2.如果函数声明和定义同时存在,则默认参数只能出现在声明中,否则会报重复定义的错误
void sum(int a=4, int b=5);
int main ()
{
sum();
}
void sum(int a, int b)
{
cout << a+b;
}
3.默认参数可以为常量或全局变量还可以是函数名
void test(int a)
{
cout << a;
}
void tt(int a, void(*p)(int) = test)//相当于传了个函数的指针,给它默认值为test函数
{
p(a);
}
int main()
{
void(*p)(int) = test;//p为指向test函数的指针,void是函数的返回值,后面的int是函数参数类型
p(10);
tt(1, test);
return 0;
}
4.C语言也可以用函数名作参数,但C没有默认参数
5.函数重载和默认参数可能会产生二义性
//属于函数重载,但调用时如果只传一个参数,则会产生二义性
void func(int a, int b=9);
void func(int a);
extern “C”
- C++程序调用C语言写的函数或引用.C文件时,必须在调用前加上extern "C"关键字,被extern "C"修饰的代码会按照C语言的方式编译
- 函数声明和实现同时存在时,extern "C"应加在声明前面,不能加在实现前面
//会报错,注意C语言没有函数重载
extern "C" void func(int a);
extern "C" void func(void);
//下面这样就可以
extern "C" void func(int a);
void func(void);//以C++方式编译,会进行name mangling
- 作用:如果C++要调用C语言写的第三方开源库中的函数,则需要首先用extern "C"进行函数声明,也可包含.c头文件,直接将extern "C"写在#include "math.h"之前,还可以将extern "C"写进头文件里,便于多个C++程序调用该头文件,但这样做会使得C程序无法包含该头文件,因为C语言中无法识别extern “C”,因此可以用一下方法
//下面是.c头文件
#ifdef _cplusplus//每个C++程序的开头都会默认定义这个宏,因此可通过判断是否定义了这个宏来决定是否使用extern "C"
extern "C"
{
#endif
void func(int a);
int func2(void);
#ifdef _cplusplus
}
#endif
4.使用 #ifndef,#define, #endif来防止头问件中的内容被重复包含
而#pragma once可以防止整个文件的内容被重复包含
//或者直接在最开始写#pragma once
#ifndef _MATH_H
#define _MATH_H
#ifdef _cplusplus
extern "C"
{
#endif
void func(int a);
int func2(void);
#ifdef _cplusplus
}
#endif//_cplusplus
#endif//_MATH_H
内联函数
inline void fun1(int a, int b)
{
cout << a + b;
}
inline void fun2(int a)
{
if (a == 1)
cout << 1;
else
fun2(a-1);
}
int main()
{
int a, b;
cin >> a >> b;
fun1(a,b);//相当于直接把cout << a + b;复制到此处
fun2(a);//递归函数依然被调用
return 0;
}
1.可以在函数的声明和定义前面加上inline关键字使其成为内联函数,可以同时加,也可以只在声明或定义前加
2. 内联函数的作用:一般函数在被主程序调用时会首先分配栈空间,待调用结束后又回收空间,而调用内联函数时相当于直接把函数内容复制到主程序中该函数被调用位置,因此不用额外分配栈空间,提高了运行效率,但对于代码量大的函数不建议设为内联函数,这样会使主程序代码过多
3. 无论有无出口,递归函数都无法作为内联函数
4. 与宏相比,内联函数多了语法检测和函数特性,因此应尽量选用内联函数
inline void fun1(int a);
#define add(a) a+a
int main()
{
int a;
cin >> a;
fun1(++a);//函数特性使得内联函数只是传参进去,即使复制过来答案为正确
cout << add(++a);//这种情况下宏计算出错,直接复制过来相当于(++a)+(++a);
return 0;
}
void fun1(int a)
{
cout << a + a << endl;
}
5.C++中允许表达式被赋值
int a,b=2;
(a=b)=5;//valid
const
1.const使其右边数据不可更改
struct date
{
int year;
};
int main()
{
int a=4,b=5;
const int * p = &a;//const在*之前意味者*p不可更改,也即不能通过p更改a的值,但p可以更改为其他地址
*p = b;//invalid
p = &b;//valid
int const * p = &a;//const与int顺序可以调换,与上例意义相同
int * const p2 = &a;//p2不可更改为其他地址,但可以通过p2更改a的值
*p2 = b;//valid
p2 = &b;//invalid
int const * const p3 = &a;//p3是常量,*p3也是常量
const int * const p4 = &a;
date c;
date d = { 2021 };
c = d;//valid
const date* ptr = &c;
(*ptr) = d;//invalid
(*ptr).year = 7;//invalid
ptr->year = 8;//invalid
ptr = &d;//valid
return 0;
}
引用
1.在C++中Reference可以起到跟指针类似的功能
引用相当于变量的别名(基本数据类型,枚举,结构体,类,数组等都可以有引用)
对引用做计算就是对引用所指向的变量做计算
在定义的时候就必须初始化,一旦指向了某个变量,就不可以再改变
可以利用引初始化另一个引用,相当于某个变量的多个别名
不存在:引用的引用,指向引用的指针,引用数组
int n = 2;
//定义了一个n的引用,ref相当于n的别名
int &ref = n;
int &res=ref;
int &req=res;
ref = 45;
res+=45;
req+=45;
cout << n;
//引用数组
int a[]={1,2,3};
int &ref[3]=a;
int* const &ref=a;
//valid,数组名是常指针不能指向其他地址,所以要用常引用,且这种写法不用标明数组长度
引用的作用:
#include <iostream>
using namespace std;
void swap(int &a, int &b)
{
int tmp=a;
a = b;
b = tmp;
}
int main()
{
int a = 50, b = 2;
swap(a, b);
cout << a << b;
return 0;
}
const reference
int age=9;
const int &ref=age;//常引用不能通过ref修改age的值和const int*一样
const int &ref=30;//常引用可以赋值常量,表达式
int &ref=30;//invalid,一般引用相当于指针不能等于常量,需赋值地址或变量
常引用作为函数参数时可以接受const(10,20)和非const实参(a,b)
(此规则也适用于const指针)
void sum(const int &a, const int &b)
{
cout << a+b << endl;
}
int main ()
{
int a=4,b=5;
sum(a,b);
sum(4,5);//如果sum函数参数是一般引用的话,就不能传常量进去了
}
可以和非const构成重载
void sum(int &a,int &b);传入非const时调用这个
void sum(const int &a,const int &b);传入const int a或4时调用这个
当常引用指向不同类型的数据时,会产生临时变量,即引用指向的并不是初始化时的那个变量
int age=10;
const int &ref=age;
age=30;//此时ref的值也会变为30,因为相当于ref指针依然指向age所在存储空间
//但如果刚才是const double &ref=age改变age后,ref的值依然为10,
//因为相当于重新定义了一个double tmp=10;const double &ref=tmp;ref的指向改变了
面向对象
1.struct,class都可以用来定义类,但struct的默认成员权限是public,class的默认成员权限是private
2.内存可以分为:栈空间,堆空间,代码区,全局区
每次定义类对象时,只为成员变量在栈空间中分配内存,不包括成员函数,
成员函数为类的公共接口,只在代码区中为其分配一次空间
3.对象调用类方法时会将存有对象地址的this指针的地址发送给函数,函数取出this根据this来访问该对象的成员变量(类方法访问成员变量时可以省略this->member中的this,直接访问)
内存
1.指针的本质(x86环境,32bit)

2.堆空间的初始化
malloc,free
//malloc申请到空间后不会初始化
int size=sizeof(int)*10
int* p=(int*)malloc(sizeof(int)*10);
//可以使用memset()函数初始化,memset()也可用于清零
memset(p,0,size);
//如果试图初始化为其他数比如memset(p,1,size)
//代表从p地址开始的连续4个字节中的每一个字节都设置为1
//00000001 00000001 00000001 00000001这是一个极大的int整型
//所以一般初始化为0
cout << p << endl;
free(p);
new,delete
int* p=new int;
int* p=new int();//初始化*p为0
int* p=new int(5);//初始化为5
构造函数与析构函数
- 构造函数:
函数名应与类同名,无返回值(不能在前面写void),可以有参数,可以重载,可以有多个构造函数
在定义类对象时默认启动
在某些特定情况下,编译器才会为没有自定义构造函数的类生成空的无参的构造函数
class Student
{
public:
int m_score;
Student()
{
m_score=99;
}
Student(int score)//重载构造函数
{
m_score=score;
}
};
int main ()
{
Student student;//说明调用的是无参的构造函数
cout << student.m_score << endl;
Student person(85);//调用有参的构造函数
cout << person.m_score;
Student* p=new Student(5);
//使用new会调用构造函数并且会调用含参构造函数
Student* p1=(Student*)malloc(sizeof(Student));/
//使用malloc是不会调用构造函数的
return 0;
}
注意定义对象与函数声明的区别:
Student g_s1;//调用无参构造
Student g_s2();//实际上是个函数声明,所以不定义对象也不调用构造函数
Student g_s3(5);//调用含参构造
int main ()
{
Student s1;//调用无参构造
Student s2();//实际上是个函数声明
Student s3(10);//调用含参构造
Student* p1=new Student;//调用无参构造
Student* p2=new Student();//调用无参构造
Student* p3=new Student(20);//调用含参构造
2.成员变量的初始化
如果没有自定义构造函数且类中没有初始化:
则全局区成员变量初始化为0,加了括号的堆空间成员变量也初始化为0
如果自定义了构造函数即便为空函数:
除了全局区依然初始化为0,其他内存空间都不会默认初始化
Student g_s1;//全局区 :成员变量初始化为0
int main ()
{
Student s1;//栈空间:没有初始化成员变量
Student* p1=new Student;//堆空间:没有初始化成员变量
Student* p2=new Student();//堆空间:成员变量初始化为0
cout << g_s1.m_score << endl;//valid 0
cout << s1.m_score << endl;//invalid
cout << p1->m_score << endl;//valid cc
cout << p2->m_score << endl;//valid 0
数组初始化:
Student *pp=new Student[3];//不会被初始化
Student *pp1=new Student[3]();//3个对象的成员变量都初始化为0
Student *pp2=new Student[3]{};//3个对象的成员变量都初始化为0
将对象整体初始化为0操作:
class Student
{
public:
int m_score1;
int m_score2;
int m_score3;
Student()
{
memset(this,0,sizeof(Student));
}
};
3.析构函数
无返回值,无参,不可以重载,有且只有一个析构函数
通过malloc分配的对象free时不会调用析构函数
构造和析构函数要声明为public才能被外界使用
class Student
{
public:
int m_score;
Student()//新的对象诞生的象征
{
m_score=99;
}
~Student(){};//一个对象销毁的象征
};
4.内存管理
堆空间的释放放在析构函数中便会在每次对象销毁时自动进行,省去主函数中手动释放的过程
class Student
{
public:
int m_score;
int *m_ptr=new int;
Student()
{
m_score=99;
*m_ptr=98;
};
~Student()
{
delete m_ptr;
};
};
int main ()
{
{
//创建对象时调用构造函数
Student student;
}//结束后对象销毁时对象中的成员变量占用的栈空间被回收,
//而后再调用析构函数,如果分配了堆空间,便可在析构函数中释放占用的堆空间
return 0;
}
命名空间
如果存在两个类名相同会造成类名冲突,可将其放进不同的命名空间中
命名空间还可以嵌套
namespace win
{
int age;//任何处于命名空间中的元素调用时都必须声明命名空间,依然是全局变量
void func(){};
class Student
{
public:
Student(){
cout << "win::Student" << endl;
};
~Student(){
cout << "~win::Student" << endl;
};
};
}
class Student
{
public:
Student(){
cout << "Student" << endl;
};
~Student(){
cout << "~Student" << endl;
};
};
int main ()
{
//或者using namespace win;
//age=10;
using win::func;
func();
win::age=10;
{
win::Student student;
}
{
Student person;
}
return 0;
}
默认全局命名空间
//所有程序都存在于默认的全局命名空间中,该空间没有名字,调用时加上::即可
void func()
{
cout << "func" << endl;
}
namespace Win
{
void func()
{
cout << "Win::func" << endl;
};
}
int main ()
{
::func();//此时调用存在于默认命名空间的函数
return 0;
}
继承
对于具有相同成员变量的多个类,可将相同成员放进一个父类中,定义子类时加上继承
定义子类对象分配内存空间时,其所继承的父类中的变量地址在子类变量地址之前
struct Person
{
int m_age;
void run
{
cout << "Person::run()" << endl;
}
}
struct student : Person
{
int m_score;
void study()
{
cout << "student::study" << endl;
}
}
struct worker : Person
{
int m_salary;
void work()
{
cout << "worker::work" << endl;
}
}
int main ()
{
student s;
s.run();
work.m_age=34;
}
成员访问权限
继承方式:
public: 公共的,任何地方都可以访问(struct默认)
protected: 子类内部,当前类内部可以访问
private: 私有的,只有当前类内部可以访问(calss默认)
子类内部访问父类成员的权限是以下两个权限中最小的那个:
成员本身的访问权限
上一级父类的继承方式
开发中多使用public继承
#include <iostream>
using namespace std;
struct Person
{
private:
int m_age;
};
struct student : protected Person
{
};
/*struct student
{
protected:
private:
int m_age;
}相当于加上继承方式再把父类成员复制进来*/
struct worker : public student
{
void func()
{
m_age = 20;//invalid
}
};
初始化列表
如果类的声明和实现是分开的话,初始化列表应该写在实现中
struct Person
{
int m_age;
int m_height;
Person(int age,int height)
{
m_age = age;
m_height = height;
};
/*Person(int age, int height) :m_age(age), m_height(height)//初始化的顺序与成员变量定义顺序一致
{
};与上面构造函数等价*/
};
int main()
{
Person person(20, 178);
return 0;
}
构造函数调用构造函数时必须写在初始化列表中
struct Person
{
int m_age;
int m_height;
Person() : Person(0,0){
};
/*Person()
{
Person(0,0);
};如果这样写的话实质上会新建一个对象,传入含参构造函数的是临时对象的地址而非this指针内的地址,
构造函数结束时,栈空间释放,主函数内的对象相当于未被初始化*/
Person(int age, int height)
{
m_age = age;
m_height = height;
};
};
父类构造函数:
子类构造函数默认会调用父类无参构造函数
如果子类构造函数显式调用了父类的有参构造函数,就不再默认调用父类无参构造函数
如果父类缺少无参构造函数,子类构造函数必须显式调用父类的有参构造函数
先调用父类构造再子类构造,先调用子类析构再父类析构
struct Person
{
int m_age;
Person() {
};
Person(int age) :m_age(age) {
};
};
struct Student :Person
{
Student() {
};
/*Student() :Person(10) { //显式调用含参父类构造
};*/
};
多态
父类指针可以指向子类对象,但子类指针不能指向父类对象
当子类私有继承时,父类指针不能指向子类对象
struct Person
{
int m_age;
};
struct Student :Person
{
int m_score;
};
int main()
{
//父类指针指向子类对象
Person* p = new Student;
p->m_age;
return 0;
}
多态:同一操作作用于不同对象,可以有不同解释,产生不同的结果
默认情况下,编译器只会根据指针类型调用对应的函数,不存在多态,以下代码便是默认情况
C++中的多态通过虚函数实现(在父类函数声明前加上virtual)
只要再父类中声明为虚函数,子类中重写的函数也自动变为虚函数(即子类可以省略virtual)
#include <iostream>
#include <cstdio>
using namespace std;
class Animal
{
public:
void run()
{
cout << "Animal::run()" << endl;
};
};
class Cat : public Animal//千万不要忘记public继承
{
public:
void run()//与父类中的函数返回值,名称,参数完全相同称为重写
{
cout << "Cat::run()" << endl;
};
};
class Dog : public Animal
{
public:
void run()
{
cout << "Dog::run()" << endl;
}
};
void play(Animal* p)
{
p->run();
}
int main()
{
Cat* p1 = (Cat*)new Dog;
p1->run();//指针是什么类型就会调用该类的成员,因此输出猫的函数
play(new Cat);//指针为动物类型,因此与new后的类型无关,输出均为动物函数
play(new Dog);
return 0;
}
虚表(下图为x86环境下)
创建对象后,首先为其成员变量分配栈空间,然后在栈顶额外分配四个字节内存用于存储虚表地址,虚表中存储最终需要调用的虚函数的地址也即子类重写的父类虚函数地址
创建对象使用父类指针指向子类对象时:
默认先调用父类构造函数,再调用子类构造函数,但如果父类析构函数不是虚函数,则只会调用父类析构函数而不会调用子类析构函数
纯虚函数:没有函数体且初始化为0的虚函数,用来定义接口规范
抽象类:
含有纯虚函数的类,不可以实例化(不可以创建对象,可以作为指针指向子类对象)
抽象类也可以包含非纯虚函数,成员变量
如果父类时抽象类,子类没有完全重写纯虚函数,那么这个子类依然是抽象类
class Animal
{
int m_age;
virtual void run() = 0;
}
多继承
static
静态成员:被static修饰的成员变量/函数
可以通过对象(对象.静态成员)、对象指针(指针->成员)、类访问(类名::成员)
静态成员变量:
存储在数据段(全局区、类似于全局变量),整个程序运行过程中只有一份内存
对比全局变量,它可以设定访问权限(public, protected, private),达到局部共享的目的
必须初始化,但必须再类外面初始化,初始化时不能带static, 如果类的声明和实现分离(则初始化必须放到实现中)
静态成员函数:
(以下几点都是建立在创建了对象的基础上,而静态成员函数相当于全局函数,也就是并没有先建立对象再创建函数,所以不适用)
内部不能使用this指针:person.func(), this保存person对象的地址,没有创建对象所以静态函数内部不可用
不能是虚函数:多态指父类指针指向子类对象,无对象所以不行
内部不能访问非静态成员变量/函数,只能访问静态成员变量/函数
非静态成员函数额内部可以访问静态成员变量/函数
构造函数、析构函数不能是静态:创建对象时调用构造,销毁时调用析构
当声明和实现分离时,实现部分不能带static
class Person
{
public:
int m_height;
static int m_age;
static void func()
{
cout << "*" << endl;
m_height = 5;//invalid
m_age = 88;//valid
};
};
int Person::m_age = 0;
int main()
{
Person person;
person.m_age = 10;
Person::m_age = 9;
cout << person.m_age << endl;
return 0;
}
友元
模板
函数模板
template <typename T,typename A>
A add(T a, A b) {
return a/b;
}
int main() {
cout << add<int,double>(66, 20.0);
return 0;
}
编译细节
一般函数声明和实现分离时,编译器分别独立编译主函数文件和函数实现文件(头文件不参与编译)并
生成目标文件(.obj),而在main.obj中调用函数时会call函数地址,此处的地址为随意值,而函数实现文件中产生函数的地址,通过链接再对main.obj中的函数地址进行修正,最终形成.exe文件
而模板函数的声明与实现分离时,main.cpp和函数.cpp均可正常编译,但链接失败,因为函数.obj中没有函数实现的机器码,因为没有调用函数也即不知道模板函数中typename究竟是int还是什么,所以无法生成函数实现的代码,链接后,main.obj中函数地址无法修正导致编译失败
类模板
自定义可动态分配内存的数组
class Array
{
int *m_data;
int m_size;
int m_capacity;
public:
Array(int capacity=2) {
m_size = 0;
m_capacity = (capacity >2)?capacity:2;
m_data = new int[m_capacity];
}
void add(int num) {
if (m_size == m_capacity) {
int* p = new int[m_size];
for (int i = 0; i < m_size; i++) {
p[i] = m_data[i];
};
m_data = new int[m_capacity + 1];
for (int i = 0; i < m_size; i++) {
m_data[i] = p[i];
};
m_data[m_size++] = num;
}
else {
m_data[m_size++] = num;
}
}
int get(int index) {
if (index < 0 || index >= m_size) {
throw "数组下标越界";
}
else {
return m_data[index];
}
}
int size() {
return m_size;
}
//运算符重载
int operator[](int index) {
return m_data[index];
}
~Array() {
delete[]m_data;
}
};
int main() {
Array array;
array.add(19);
array.add(15);
array.add(89);
cout << array[2] << endl;
cout << array.size();
return 0;
}
类模板
#include <iostream>
using namespace std;
template <typename Item>
class Array
{
Item *m_data;
int m_size;
int m_capacity;
public:
Array(int capacity=2) {
m_size = 0;
m_capacity = (capacity >2)?capacity : 2;
m_data = new Item[m_capacity];
}
void add(Item value) {
if (m_size == m_capacity) {
Item* p = new Item[m_size];
for (int i = 0; i < m_size; i++) {
p[i] = m_data[i];
};
m_data = new Item[m_capacity + 1];
for (int i = 0; i < m_size; i++) {
m_data[i] = p[i];
};
m_data[m_size++] = value;
}
else {
m_data[m_size++] = value;
}
}
Item get(int index) {
if (index < 0 || index >= m_size) {
throw "数组下标越界";
}
else {
return m_data[index];
}
}
int size() {
return m_size;
}
//运算符重载
Item operator[](int index) {
return m_data[index];
}
~Array() {
delete[]m_data;
}
};
class Point
{
int m_x;
int m_y;
public:
void show() {
cout << '(' << m_x << ',' << m_y << ')' << endl;
}
Point(int x,int y):m_x(x), m_y(y) {
}
~Point() {
}
};
class male
{
int m_age;
public:
male(int age) :m_age(age) {
}
~male() {
}
void show() {
cout << m_age << endl;
}
};
int main() {
Array<male> array;
array.add(male(45));
array.add(male(89));
array[0].show();
cout << array.size();
return 0;
}
lambda
#include <iostream>
using namespace std;
//void func() {
// cout << "func()" << endl;
//}若想该函数只能被主函数调用则应使用lambda表达式
int main()
{
([] {
cout << "func()" << endl;
})();//相当于func()
void (*p)() = [] {
cout << "func()" << endl;
};
p();
auto p2 = [](int a, int b)->int {
return a + b;
};
cout << p2(5, 6);
return 0;
}
STL
容器、算法、迭代器
vector
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void print(int a) {
cout << a << " ";
}
int main() {
vector<int> a;
a.push_back(10);
a.push_back(25);
vector<int>::iterator pBegin = a.begin();
vector<int>::iterator pEnd = a.end();
/*for (vector<int>::iterator p = a.begin(); p < a.end(); p++) {
cout << *p << endl;
}*/
for_each(pBegin, pEnd, print);
}
string
string str = "lucycylu";
string str2 = "juddy";
str = str.append(str2);
int first,last;
first = str.find("l");
last = str.rfind("l");
cout << first << " " << last << endl;
str.replace(4, 7, "jack");
cout << str.compare(str2) << endl;//大于时返回1,小于时返回-1,等于时返回0
cout << str.substr(1, 3);
cout << str.insert(0, "bob") << endl;
cout << str.erase(7,2);//前一个数字是从第几位开始删除,后一个数字是共删除几个数字
本文详细介绍了C++编程的基础知识,包括函数重载、默认参数、externC、内联函数、const引用、面向对象、内存管理、构造函数与析构函数、命名空间、继承、成员访问权限、初始化列表、多态、静态成员、友元、模板函数和类模板。此外,还讨论了lambda表达式和STL中的vector和string。内容深入浅出,适合C++初学者及进阶学习。

766

被折叠的 条评论
为什么被折叠?



