static 不考虑类的情况
static 考虑类的情况
1、static 成员函数是类所有对象的共享成员,不属于任何类对象或类实例,所以即使给此函数加上 virutal 也是没有任何意义的,virtual 虚函数依靠 vptr 和 vtable 来处理,vptr 是一个指针,在类的构造函数中创建生成,并且只能用 this 指针来访问它,因为它是类的一个成员,而 static 成员函数不具有 this 指针,虚函数的调用关系:this -> vptr -> vtable ->virtual function
2、当声明一个非静态成员函数为 const 时,对 this 指针会有影响,而 static 成员函数没有 this 指针,所以使用 const 来修饰 static 成员函数没有任何意义
const 不考虑类的情况
const 考虑类的情况
数组和指针的区别
- 存储方式。数组在内存中连续存放,开辟一块连续的内存空间,数组是根据数组下标进行访问的;指针相当于一个变量,可以指向任意类型的数据,指针的类型说明了它所指向地址空间的内存,指针的存储空间不能确定
- 访问方式。数组的元素可以通过下标来访问,下标从 0 开始,最大下标为数组大小减 1;指针可以通过加减运算来访问它所指向的数组元素,但需要注意指针的类型,以及要访问的数组元素的类型
- 动态分配。指针可以通过malloc()和free()来分配空间和释放空间,这是动态分配空间;数组是隐式分配和删除,这是静态分配空间
- 安全性。指针使用不当会造成内存泄漏,数组使用不当会造成数组越界;指针名是变量,数组名是指针常量,所以指针 p 可以进行 p++,而数组名不可以用于 a++
override
class A {
virtual void foo();
}
class B : public A {
void foo(); // OK,对虚函数 foo() 进行重写
virtual void foo(); // OK,对虚函数 foo() 进行重写
void foo() override; // OK,对虚函数 foo() 进行重写
}
final
class Base {
virtual void foo();
};
class A : public Base {
void foo() final; // foo 被 override 并且是最后一个 override,在其子类中不可以重写
};
class B final : A { // 指明 B 是不可以被继承的
void foo() override; // Error: 在 A 中已经被 final 了
};
class C : B // Error: B is final
{};
string str1("I am a string"); // 语句1 直接初始化
string str2(str1); // 语句2 直接初始化,str1是已经存在的对象,直接调用拷贝构造函数对str2进行初始化
string str3 = "I am a string"; // 语句3 拷贝初始化,先为字符串创建临时对象,再把临时对象作为参数,使用拷贝构造函数构造str3
string str4 = str1; // 语句4 拷贝初始化,这里相当于隐式调用拷贝构造函数,而不是调用赋值运算符函数
为了提高效率,允许编译器跳过创建临时对象这一步,直接调用构造函数构造要创建的对象,这样拷贝初始化就完全等价于直接初始化了,但是需要辨别两种情况
- 拷贝构造函数为 private 时:语句 3 和语句 4 在编译时会报错
- 使用 explicit 修饰构造函数时:如果构造函数存在隐式转换,编译时会报错
// 文件1: main.cpp
extern int shared_var; // 声明一个外部整型变量
int main() {
shared_var = 10; // 使用外部变量
return 0;
}
// 文件2: shared.cpp
int shared_var = 0; // 定义一个全局整型变量
// 文件1: main.cpp
extern void print_message(); // 声明一个外部函数
int main() {
print_message(); // 调用外部函数
return 0;
}
// 文件2: print.cpp
#include <iostream>
void print_message() { // 定义一个函数
std::cout << "Hello, World!" << std::endl;
}
// 文件1: main.cpp (C++ 代码)
extern "C" void print_message(); // 使用 extern "C" 声明一个外部函数
int main() {
print_message(); // 调用外部函数
return 0;
}
// 文件2: print.c (C 代码)
#include <stdio.h>
void print_message() { // 定义一个函数
printf("Hello, World!\n");
}
都是指向无效内存区域(“不安全不可控”)的指针,访问行为将会导致未定义行为
int* p; // 未初始化
std::cout<< *p << std::endl; // 未初始化就被使用
int* p = nullptr;
int* p2 = new int;
p = p2;
delete p2;
重载(overload)
重写/覆盖(override)
隐藏(hide)
对象的初始化和清理工作是编译器强制要做的事情,因此如果程序员不提供构造和析构,编译器会主动提供,但编译器提供的构造函数和析构函数是空实现,构造函数和析构函数作用如下:
构造函数可以有参数,因此可以发生重载,而析构函数没有参数,因此不可以发生重载
构造函数的分类及调用
// 1、构造函数分类
class Person {
public:
// 1.1 无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
// 1.2 有参构造函数
Person(int a) {
age = a;
cout << "有参构造函数!" << endl;
}
// 1.3 拷贝构造函数
Person(const Person& p) {
age = p.age;
cout << "拷贝构造函数!" << endl;
}
// 析构函数,在释放内存之前调用
~Person() {
cout << "析构函数!" << endl;
}
public:
int age;
};
// 2、构造函数的调用
// 2.1 调用无参构造函数
void test01() {
Person p; // 调用无参构造函数
}
// 2.2 调用有参的构造函数
void test02() {
// 2.2.1 括号法,最常用
Person p1(10);
// 注意 1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明
//Person p2();
// 2.2.2 显式法
Person p2 = Person(10);
Person p3 = Person(p2);
//Person(10)单独写就是匿名对象 当前行结束之后,马上析构
// 2.2.3 隐式转换法
Person p4 = 10; // Person p4 = Person(10);
Person p5 = p4; // Person p5 = Person(p4);
// 注意 2:不能利用拷贝构造函数初始化匿名对象,编译器认为是对象声明
// Person p5(p4);
}
int main() {
test01();
//test02();
system("pause");
return 0;
}
拷贝构造函数调用时机
// 1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01() {
Person man(100); // p 对象已经创建完毕
Person newman(man); // 调用拷贝构造函数
Person newman2 = man; // 拷贝构造
//Person newman3;
//newman3 = man; // 不是调用拷贝构造函数,赋值操作
}
// 2. 值传递的方式给函数参数传值
// 相当于 Person p1 = p;
void doWork(Person p1) {}
void test02() {
Person p; //无参构造函数
doWork(p);
}
// 3. 以值方式返回局部对象
Person doWork2() {
Person p1;
cout << (int *)&p1 << endl;
return p1;
}
void test03() {
Person p = doWork2();
cout << (int *)&p << endl;
}
int main() {
//test01();
//test02();
test03();
system("pause");
return 0;
}
构造函数调用规则
类成员初始化
class Person {
public:
// 1、赋值初始化
//Person(int a, int b, int c) {
// m_A = a;
// m_B = b;
// m_C = c;
//}
// 2、成员列表初始化
Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c) {}
private:
int m_A;
int m_B;
int m_C;
};
(派生类)构造函数的执行顺序
析构函数顺序正好与上面顺序相反
构造函数、拷贝构造函数和赋值操作符的区别
拷贝构造函数是函数,赋值运算符是运算符重载,拷贝构造函数会生成新的类对象,赋值运算符不能
#include <iostream>
using namespace std;
class Person {
public:
// 无参(默认)构造函数
Person() {
cout << "non para construct!" << endl;
}
// 有参构造函数
Person(int age, int height) {
cout << "have para construct!" << endl;
m_age = age;
m_height = new int(height);
}
// 拷贝构造函数
Person(const Person& p) {
cout << "copy construct!" << endl;
// 如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
m_age = p.m_age;
m_height = new int(*p.m_height);
}
// 析构函数
~Person() {
cout << "~!" << endl;
if (m_height != NULL) {
delete m_height;
}
}
public:
int m_age;
int* m_height;
};
int main() {
Person p1(18, 180);
Person p2(p1);
cout << "p1 age: " << p1.m_age << " height: " << *p1.m_height << endl;
cout << "p2 age: " << p2.m_age << " height: " << *p2.m_height << endl;
system("pause");
return 0;
}
have para construct!
copy construct!
p1 age: 18 height: 180
p2 age: 18 height: 180
~!
~!
访问权限
继承权限:protected 继承和 private 继承
public继承
- 公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,都保持原有的状态,而基类的私有成员任然是私有的,不能被这个派生类的子类所访问
友元(friend)
- 有些私有属性也想让类外一些特殊的函数或者类进行访问,就需要用到友元的技术
- 友元的目的就是让一个函数或者类访问另一个类中私有成员
C++ 隐式转换
- 所谓隐式转换,是指不需要用户干预,编译器私下进行的类型转换行为
- 通过隐式转换,可以直接将一个子类的对象使用父类的类型进行返回
- 隐式转换发生在从小 --> 大的转换中:char --> int、int --> long
- 在构造函数声明的时候加上explicit关键字,能够禁止隐式转换
常见的异常
异常处理方式
double m = 1, n = 0;
try {
cout << "before dividing." << endl;
if (n == 0)
throw - 1; // 抛出 int 型异常
else if (m == 0)
throw - 1.0; // 拋出 double 型异常
else
cout << m / n << endl;
cout << "after dividing." << endl;
}
catch (double d) {
cout << "catch (double)" << d << endl;
}
// 1. 值传递
void mySwap01(int a, int b) {
int temp = a;
a = b;
b = temp;
}
// 2. 指针传递
void mySwap02(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 3. 引用传递
void mySwap03(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int a = 10;
int b = 20;
mySwap01(a, b);
cout << "a:" << a << " b:" << b << endl;
mySwap02(&a, &b);
cout << "a:" << a << " b:" << b << endl;
mySwap03(a, b);
cout << "a:" << a << " b:" << b << endl;
system("pause");
return 0;
}
// 把 expression 转换为 type-id 类型
static_cast <type-id> (expression)
// type-id 必须是类的指针、类的引用或者 void*
dynamic_cast <type-id> (expression)
const_cast<type_id> (expression)
reinterpret_cast<type-id> (expression)
非静态全局变量和静态全局变量
- 都是采取静态存储方式(全局变量前再冠以 static 就构成静态全局变量)
- 非静态的全局变量在各个源文件中都是有效的,而静态全局变量只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它,静态全局变量只初始化一次,防止在其他文件单元被引用
#include <iostream>
using namespace std;
int f(int n) {
cout << n << endl;
return n;
}
void func(int param1, int param2) {
int var1 = param1;
int var2 = param2;
// 如果将 printf 换为 cout 进行输出,输出结果则刚好相反
printf("var1 = %d, var2 = %d", f(var1), f(var2));
}
int main(int argc, char* argv[]) {
func(1, 2);
return 0;
}
2
1
var1 = 1, var2 = 2
栈区(向下增长)
堆区(向上增长)
自由存储区
全局/静态存储区
常量存储区
代码区
程序运行前:在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域
该区域的数据在程序结束后由操作系统释放
程序运行后
C++ 内存的三种分配方式
- 从静态存储区分配:此时的内存在程序编译的时候已经分配好,并且在程序的整个运行期间都存在。全局变量,static 变量等在此存储
- 在栈区分配:相关代码执行时创建,执行结束时被自动释放。局部变量在此存储。栈内存分配运算内置于处理器的指令集中,效率高,但容量有限
- 在堆区分配:动态分配内存。用 new/malloc 开辟,delete/free 释放。生存期由用户指定,灵活,但有内存泄露等问题
因篇幅问题不能全部显示,请点此查看更多更全内容