C++对C的拓展-3.22笔记
全部代码在VS中完成
在哔哩哔哩学习的这个老师的C++面向对象高级语言程序设计教程(118集全)讲的很不错(真的!!!),C语言也是在这个老师的带领下学习的
#include<iostream>
using namespace std;
//在局部要访问全局变量要加作用域::
int num = 10;
void test01(){
int num = 20;
cout << "局部变量num=" << num << endl;
cout << "全局变量num=" << ::num << endl;
}
//命名空间
/*命名空间只能在全局范围定义
* 命名空间可以嵌套命名空间A::B::a
* 命名空间是开放的,可以随时把新的成员加入已有的命名空间
*/
namespace A {
int data1 = 50;
}
namespace B {
int data1 = 60;
}
void test02() {//加作用域是最安全最简单的方法
cout << "输出A中的data1:" << A::data1 << endl;//50
cout << "输出B中的data1:" <<B::data1 << endl;//60
}
//控制输出数的精度要用以下格式
//#include<iomanip>
//cout<<fixed<<setprecision(4)<<endl;
//声明和实现可分离
//命名空间只是整体的框架,所以函数的实现不用在命名空间中,只是申明一下
namespace C {
int a;
void set_a(int data);
int printf_a(void);
}
void C::set_a(int data) {//函数在外面实现时需要加作用域
C::a = data;
}
int C::printf_a(void) {
return C::a;
}
void test03(){
C::set_a(520);
cout << "a = " << C::printf_a() << endl;
}
//无命名空间
//之前访问全局变量时使用::就是使用的一整个无命名空间
//内部连接只能在当前源文件用
//外部连接可以在外部源文件使用
//命名空间别名
namespace longlongname {
int a = 456;
void put(void) {
cout << "helloworld" << endl;
}
}
void test04() {
namespace stortname = longlongname;//赋值语句
cout << "longlongname::a=" << stortname::a << endl;
longlongname::put();
stortname::put();
}
//using修饰命名空间里面的某些数据或方法
namespace D {
int prameA = 12;
int prameB = 55;
void fun_A() {
cout << "hello fun_A" << endl;
}
void fun_B() {
cout << "hello fun_B" << endl;
}
}
void test05() {
//使用作用域::
cout << "D::prameA=" << D::prameA << endl;
cout << "D::prameB=" << D::prameB << endl;
//使用using
using D::prameA;//表示以后使用D中的prameA
using D::fun_A;//表示以后使用D中的fun_A
fun_A();
cout << prameA << endl;
//使用using如果有同名的会造成二义性
}
//using声明碰到函数重载
//函数名相同,但是函数参数不同即一个函数名多种方法叫做函数重载,多态的体现
//c++三大特性:封装,继承,多态
//如果命名空间包含一组用相同名字重载的函数,using声明就声明了这个重载函数的所有集合
//using修饰整个命名空间
namespace E {
int a = 105;
int b = 2;
int c = 3;
}
void test06() {
using namespace E;
//表示之后所有都使用E这个命名空间的内容
//强调的是命名空间名
//先查看当前普通变量a,如果没有再查看命名空间有没有a
cout << a << endl;
int a = 88888;
cout << a << endl;//就近原则
cout << b << endl;
cout << c << endl;
}
//全局变量检测增强
/*在C语言中
* int a=10;有赋值当做定义
* int a;没有赋值当作申明
*/
/* 在C++中
* 申明必须要用extern
* extern int a;
*/
//C++中所有变量和函数必须有类型
/*在C语言中
* int fun1(i){}
* 其中i没有类型,可以是任何类型
* int fun2(){}
* 其中没有写参数,代表可以传任何类型的实参,只是接收不了,因为没有形参
*/
/*在C++中不可以这样
*/
//严格的类型转换
//在C++中不同类型一般不能直接赋值(基本数据类型可以,但是指针类型不可以),需要相应的强制转换
//struct(结构体)类型加强
/*1.C++中定义结构体类型的时候可以不加struct
* C语言:struct student Bob;
* C++语言:struct Bob;
* 2. C++中结构体成员可以是函数,而C语言不行
*/
struct student {
int id_num;
void set_num(int data) {
id_num = data;
}
};
void test07(){
student Bob;
Bob.set_num(158);
cout << "id_num = " << Bob.id_num << endl;
}
//新增bool类型
//bool的变量只能赋值为true(非零)false(0)
void test08() {
bool num;
num = true;
cout << "true = " << true << endl;
cout << "false = " << false << endl;
}
//三目运算符功能增强
/*C语言中的三目运算符
*a>b ? a:b
* 返回的是a或者b的值
*/
//在C++中三目运算符返回的是a或b的引用(变量名)
void test09(){
int a = 58;
int b = 69;
(a > b ? a : b) = 33;
cout << "a = " << a << "b = "<< b <<endl;
}
//const增强(重要)
/*
在C语言中const修饰的是不变的变量而不是常量
既然是变量那么系统就会为它开辟空间
解释以下const int arrsize=10;
int arr[arrsize];在C语言中会报错
因为int arr[arrsize];在编译阶段系统就要为数组开辟空间,此时系统还不知道arrsize多大
而赋值这个动作要在运行阶段才可以完成,所以数组的元素个数不可以用变量名
*/
//C++的const
//1.C++中如果使用常量初始化const的变量,那么该变量就是符号常量并且放入符号常量表中
//const int num=10;这里的num是符号常量,被放入符号常量表中,num没有空间
//如果这时对num取地址,系统才会为num开辟空间,此时num才会变成变量
void test10() {
const int num = 50;
int* p = (int*)#//必须强制类型转化,或者写成const int* p=#
*p = 77;
cout << "*p = " << *p << endl;//结果为77,因为*p修改了空间内容
cout << "num = " << num << endl;//结果为50,因为num这个变量名已经存在在符号常量表中,只要你取num这个变量名,那么就会在符号常量表中取找
}
//2.如果用普通变量初始化const修饰的变量,系统会立即为const修饰的变量开辟空间(就没有了符号常量表)
/*
* int a=66;
* const int p=a;
*/
void test11() {
int a = 98;
const int num = a;
int* p = (int*)#
*p = 77;
cout << "*p = " << *p << endl;//77
cout << "num = " << num << endl;//77
}
//3.const修饰全局变量a,如果使用常量初始化,那么变量a被分配到文字常量区,不在全局区,不可以进行赋值,赋值会发生段错误
//4.const如果修饰的是自定义数据类型(结构体,枚举,共用体)的变量,直接开辟空间
struct stu {
int num;
char name[32];
};//结构体结尾需要加分号
void test12() {
const stu Bob = { 500,"Bob" };
stu* p = (stu*)&Bob;
p->num = 600;
cout << Bob.name << Bob.num << endl;
}
//5.尽量以const替换宏(#define MAX 100;)
//const有类型,可以进行编译器安全检查,如果出错会爆出MAX这个名字,而#define无类型,不可以进行类型检查(出错只会出现100这个值)不好检查哪里出错
//const有作用域,而#define不重视作用域,不能作为类的成员,破坏封装性,不符合C++的特性
//在C++中能用引用(给变量名取个别名)绝不用指针(重要)
//1.编译器不会为别名开辟新的空间,但是使用指针就必须要为指针开辟空间,所以使用别名可以提高空间利用率
/*格式(给num取个别名为b)
* int num=10;
* int &b=num;//&不是取b的地址,只是描述b是num的别名
*/
//别名一般运用于函数传参,既可以将数传递进去,又可以节约空间
/*取别名的步骤
* 1.1.&修饰别名
* 1.2.要对那个变量取别名,就定义那个变量
* 1.3.从上往下整体替换
*/
void test13() {
int a = 15;
int& b = a;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
//a和b占用同一份空间
cout << "&a = " <<&a << endl;
cout << "&b = " << &b << endl;
}
void test14() {
int arr[5] = { 1,2,3,4,5 };
int (&new_arr)[5] = arr;//要加小括号,因为中括号的优先级最大
cout << "arr[2] = " << arr[2] << endl;
cout << "new_arr[2] = " << new_arr[2] << endl;
//a和b占用同一份空间
cout << "&arr = " << &arr << endl;
cout << "&new_arr = " << &new_arr << endl;
}
//2.引用必须初始化
//int &b;//非法的
//3.引用一旦确定是谁的别名就不能更改
//4.引用作为函数参数,可以替代指针变量
void swap_int01(int a1, int b1) {//值传递
int temp = a1;
a1 = b1;
b1 = temp;
}
void swap_int02(int *a1,int *b1) {//使用指针
int temp = *a1;
*a1 = *b1;
*b1 = temp;
}
void swap_int03(int &a1, int &b1) {//使用引用
int temp = a1;
a1 = b1;
b1 = temp;
}
void test15() {
int a = 1;
int b = 2;
cout << "a = " << a << ",b = " << b << endl;
swap_int01(a, b);//失败,因为传的值,而不是地址,没有办法将函数外免得值交换
cout << "1.a = " << a << ",b = " << b << endl;
swap_int02(&a, &b);
cout << "2.a = " << a << ",b = " << b << endl;
swap_int03(a, b);
cout << "3.a = " << a << ",b = " << b << endl;
}
/*引用的好处
* 1.函数内部直接通过引用操作外部变量的值
* 2.省去了指针的操作
* 3.函数形参不会用于新的空间(节约空间)
*/
//常引用
//不能通过a修改空间的值
void test16() {
//给10这个不变常量取个别名
//int &a=10//不可以,因为a是int,而10是const int
//int &a=(int)10//也不可以C++是强语法
const int& a = 10;
cout << "a =" << a << endl;
}
//常引用的运用场景:常引用作为函数参数即节约了空间,又防止了函数内部修改外部空间变量的值
void printf_data(const int a) {
//此时在函数内部就不可以修改在外部作为变量的值
cout << "num = " << a << endl;
}
void test17() {
int num = 10;
printf_data(num);
}
//引用作为函数的返回值类型(非常重要)
//1.通过函数返回值在外界操作函数内部申请的空间(在C语言中是通过返回一个指针从而控制函数内部变量)
int& get_data(void) {
static int data = 999;
//不要返回普通局部变量的引用,因为在函数执行完成之后会释放空间,不可以对一个非法空间取别名,会出现段错误
//加一个static修饰,生命周期更长
return data;//返回什么就是给谁取别名
}
void test18() {
int& a = get_data();//用什么接就是给返回值取了什么别名
cout << "a = " << a << endl;
}
//2.引用作为函数的返回值类型,可以完成链式操作
struct print {
print& printf_int(print& ob, int value) {
cout << value << endl;
return ob;
}
};
void test19(){
print obj;
obj.printf_int(obj, 10).printf_int(obj, 20).printf_int(obj, 30);
//obj.printf_int(obj, 10)返回的是ob,然而obj=ob,所以拼接上去就可以无限下去
}
//引用的本质是常量指针
/*int a=10;
* int &b=num;的低层实现
* int * const b=#//const修饰变量名表示之后不可以随便改变指针的指向,b(地址)只读,*b指向的空间内容可读可写
*/
//指针变量的引用
void test20() {
int num = 999;
int* p = #//取别名的步骤,1.&修饰别名 2.要对那个取别名就定义那个变量 3.从上往下整体替换
int*& new_p = p;
cout << " *p = " << *p << endl;
cout << "*new_p = " << *new_p << endl;
}
//实际运用(给p开辟空间让p指向)
void get_memory01(int** p1) {//int **p1=&p;
//*p1==p
*p1 = (int*)calloc(1, sizeof(int));
**p1 = 100;
}
void get_memory02(int* &p1) {//int* &p1=p;
//p1==p;
p1 = (int*)calloc(1, sizeof(int));
*p1 = 100;
}
void test21() {
int* p = NULL;
//get_memory01(&p);
get_memory02(p);
cout << "*p = " << *p << endl;
}
//内联函数(inline修饰的函数)(在编译阶段完成替换)(在函数定义的位置加上inline才可以,申明时加上不可以)
inline int myAdd(int x,int y) {
return x + y;
}
//1.内联函数为了继承宏函数的效率,没有函数的开销,然后又可以像普通函数那样,可以进行参数返回值类型的安全检查,又可以作为成员函数
/*
*2.内联函数特点
* 2.1.能保证参数的完整性
* 2.2.有作用于的限制,可以成为类的成员
* 2.3类中的成员函数默认都是内联函数(不用inline)
*/
/*
* 3.宏函数和内联函数的区别:
* 宏函数:
* 预处理阶段完成替换,没有出入栈的开销,不能保证参数完整性,没有作用域限制,不能作为类的成员
* 内联函数:
* 编译阶段完成替换,没有出入栈的开销,能保证参数完整性,有作用域限制,能成为类的成员
*/
/*
*4.以下情况编译器不会考虑将函数进行内联编译(内联函数只是对编译器的一个建议):
* 在函数中不能存在任何形式的循环语句,不能存在过多的的条件判断语句,函数体不能过于庞大,不能对函数精心取地址操作
*/
//默认参数(缺省参数)(了解)
//1.C++在申明函数原型时可为一个或两个参数指定默认(缺省)的参数值,当函数调用的时候如果没有传参,编译器会自动用默认值代替
int my_add(int a,int b = 100) {//如果函数调用时,不给函数b传参,那么b的值为100
return a + b;
}
void test22() {
cout << "my_add(10,20) = " << my_add(10, 20) << endl;
}
/*2.函数默认参数从左往右,如果一个参数设置了默认参数,那么这个参数之后的参数都必须设置默认参数
*int fun(int a,int b=500,int c=50){}可以
*int fun(int a=95,int b=66,int c=50){}可以
*int fun(int a,int b,int c=50){}可以
*int fun(int a,int b=99,int c=50){}不可以
*/
//3.要在申明处设置默认值(一个函数可能在不同的文件中使用,即不同的场景,需要的默认值不一定相同,而在不同文件中使用函数时需要有申明,此时设置默认函数最为合适)
//函数的占位参数(了解)
/*
* 1.1.C++在申明函数时,可以设置占位参数
*1.2.占位参数只有参数类型声明,而没有参数名申明
*1.3一般情况下,在函数内部无法使用占位参数
* 1.4占位参数可以设置为缺省参数(默认参数处在传参时可以少传)
* void testfun(int a,int b,int=100){}
* 1.5什么时候用占位参数,在后面的操作重载的后置++要用到这个
*/
void testfun(int a,int b,int){
cout<<"a + b = "<<a + b <<endl;
}
void test23() {
testfun(10, 20, 30);
}
//函数的重载(多态的体现)(重要)
//1.函数的重载条件:函数名相同,函数个数,函数类型,函数顺序可以不同,就可以重载
void Fun(int a) {
cout << "int : a = " << a << endl;
}
void Fun(int a,int b) {//函数个数不同
cout << "int int : a = " << a <<", b = "<< b << endl;
}
void Fun(int a, float b) {//函数类型不同
cout << "int float : a = " << a << ", b = " << b << endl;
}
void Fun(float a, int b) {//函数顺序不同
cout << "float int : a = " << a << ", b = " << b << endl;
}
void test24() {
Fun(10);
Fun(10, 20);
Fun(10, 20.1f);
Fun(10.1f,20);
}
//2.返回值类型不同不可以占位重载条件
//3.函数重载和缺省参数同时出现,一定要注意二义性(ambiguous)
void fun01(int a) {
cout << "int : a = " << a << endl;
}
void fun01(int a,int b=100) {
cout << "int int : a = " << a <<", b = " << b << endl;
}
void test25() {
//fun01(50);会出错,这个会找到两个函数,不知道该调用哪一个
fun01(20, 30);
}
/*4.函数重载的原理
* 在C语言中,函数的入口地址是函数名决定
* 在C++中,函数的入口地址是由函数名和函数个数,顺序,类型共同决定的(编译器会将同名并且其他不同的函数修改成不同的函数名,这样就不会违背C语言的底层逻辑,函数名就代表函数入口地址)
*/
int main(int argc, char* argv[])
{
test25();
return 0;
}