首页 > 编程语言 >C++学习笔记

C++学习笔记

时间:2023-09-05 21:34:46浏览次数:31  
标签:const 函数 int void 笔记 学习 引用 指针 C++

C++:

C的编译器叫gcc,C++的编译器叫g++

c++最容易发生的问题是内存泄漏问题,例如释放p所指的空间时,应该是

free(p);
p = NULL;

很多人忘记将p归零,这样下次不小心调用了p就会出现内存泄漏问题,如果要把释放内存写成函数,示例如下(两层指针)

void free_mem(int **pp){
    if(pp == NULL){// 确保指针的指针不是0
        return;
    }
    if(*pp != NULL){// 要释放的指针不为0
        free(*pp);
    }
    *pp = NULL; // 将原指针归零
}

命名空间:

using spacename

  1. iostream头文件提供了命名空间,标准命名空间是std

  2. 如果不加using spacename std;,使用cout和endl需要声明在哪个命名空间里,例如:std::cout << “hello” << std::endl
    或是声明使用命名空间中的一个变量:using std::cout

  3. 作用域:在函数内声明就作用在函数内,在函数外声明就作用在.cpp文件全局,可以在头文件中声明,但前提是头文件中有对应的提供了命名空间的头文件

自定义命名空间:

嵌套命名空间必须引用到底部,比如spaceA中有spaceB和变量a,spaceB中又有spaceC,spaceC中有变a,要引用spaceC中的a必须写using spaceA::spaceB::spaceC::a

新增数据类型bool:

bool类型的数据大小为1字节,true就是1,bool类型可以赋值成不为0和1的数,但是c++会自动将其变成0或1(所有非0赋值都是1)

const优化:

const修饰的变量只能被匹配的指针指向
例如:const int a = 10;此时a只能被下列1和3形式的指针指向

  1. const int *point1 表示point1所指的内存不可修改,但point1本身的地址值可以修改

  2. int * const point2 表示point2本身地址值不可修改,但其所指内存可以修改

  3. const int* const point3 表示point3本身地址不可修改,且point3所指的内存也不可修改

  4. 被const修饰的变量不可以通过指针修改

即const直接跟变量名,修饰的是指针;const在(指针)类型前,修饰的是指针内存

指针与const交互

  1. 在c++中,const修饰的常量在编译时会被添加到符号表中,而不是为其开辟一块新的内存空间。类似于键值对,变量名与对应的值一一对应,当程序遇到常量时会直接将其替换为对应的值,即和常数是等价的,对常数取地址显然是错误的:int* p = &1(符号表是在一段只读内存中,不可获取其地址并修改)

  2. 承接1.来说,int* p = &1也是可以编译通过的,g++会临时开辟一个空间temp,让p存放temp的地址,这个地址也可以进行修改等操作

在c++中,被const修饰的变量 一定不可修改

与define的区别

  1. 代码成为可执行文件需要先预处理编译define是在预处理时就执行,是把代码完全展开丢给编译器

枚举enum规范化:

  1. 创建枚举类对象并初始化时,必须使用枚举中列出的名称,即使该名称的赋值是常数,也不能用常数代替;进行运算时,枚举类变量也必须和枚举中的名称进行运算,不能用常数代替,也就是说,枚举变量必须被赋值为枚举类值

引用:

  1. typedef不仅可以命名数据类型,还能将变量命名成其他名字,也就是起个别名,这个别名就叫引用,但定义方式不再用typedef

  2. 引用并不等于指针,定义一个指针p让它指向a,虽然可以通过p操作a,但是p本身地址值是可变的,p并不是永久指向a的,而为a定义一个引用b,则隐式指针b永远指向a,就可以说b是a的“别名”,即引用,可以直接通过引用操作变量

指针和引用的主要区别就在于,所指向的位置会不会发生改变

  1. 引用的定义方式:int& r = a及表示定义一个r为a的引用,区别于指针int*

引用的特性:

  1. 引用没有定义,是一种关系型声明,声明它和原有某一变量有关系,因此类型和原类型保持一致,且不分配内存。与被引用的变量有相同的地址。
  2. 声明时必须初始化一经声明,不可变更
  3. 可对引用再次引用。多次引用的结果是某一变量具有多个别名
  4. &符号前有数据类型时是引用,其他的时候都是取地址。
  5. 引用的大小跟指针是相等的,本质是常指针(int *const),其变量名存放在常量区

引用作参数:

  1. 引用不能当指针,进行传参时,传入引用作为参数,不能修改所指的变量。可以将引用当成变量来传参,传引用的地址(引用和变量本身具有相同地址)
  2. 引用可以作参数且具有指针同等修改能力,函数体定义如下(假设参数是引用a):
    void change_value(int& r){ // int& r = a;
     r = 10; // a = 10
    }
    

引用作为函数的返回值

int& getA(){
    int a = 10;
    return a;
}// int& temp = a;

void main(){
    int main_a = 0;
    main_a = getA();//main_a = temp;
}

在上面的例子中,getA()会创建一个a的引用temp并返回,temp在执行完a = getA()后就会销毁,本质上返回的是局部变量的指针,是危险的行为

如果把main改一下

void main(){
    int &main_a = getA();
    cout << "main_a = " << main_a << endl
}

输出结果就是乱码,因为执行完 int &main_a = getA();后,temp就被销毁了,故temp的引用main_a指向的内存也不能正确读出来

可以返回static变量的引用,因为它本质还是全局变量,在堆内存,此时可以把函数当成左值,例如:

int& getA(){
    static int a = 0;
    return a;
}
void main(){
    getA() = 10;// a = 10
}

传参方式对性能的影响:

场景:输出结构体数组中所有成员(学生类型)的信息,实现输出函数。

直接传参:

需要创建结构体形参并对传入的结构体进行值拷贝,当结构体很多时消耗大量内存

void printfS(struct student s){ // struct student s = s1
    cout << s.id << s.name << endl;
}
传指针:

需要拷贝指针,拷贝量小,但需要指针操作,不熟悉指针的人(Java转c++)可能出错

void printfS1(struct student* s) { // struct student* s = &s1
    cout << s->id << s->name << endl;
}
传引用:

需要拷贝引用,拷贝量小,且直观易懂

void printfS2(struct student& s){  // struct student& = s1
    cout << s.id << s.name << endl;
}

指针引用:

示例如下,引用可以看作一层指针,传入引用就可以修改引用所指的内存,指针本身也可以被引用

int getmem(struct teacher*& tpp) {
    struct teacher* tp = NULL;
    tp = (struct teacher*)malloc(sizeof(struct teacher));
    if (tp == NULL) {
        return -1;
    }
    tp->id = 1000;
    strcpy(tp->name, "zhang 6");
    tpp = tp;
    return 0;
}

const引用:

如果要引用const型变量,必须用const型引用来定义

int const a = 10;
int const &b = a;

三目运算符增强:

C++的三目运算符计算结果可以当左值,运算结果返回的是引用(参考java隐藏指针)
例如a<b ? a:b = 10 如果满足a < b返回的不是a的值,而是a本身(左值),并且可以被赋值为10

inline内联函数:

适用于简单多次调用的函数,过于复杂的函数(内部有循环)即使写成内联函数也可能不生效

功能是牺牲用空间效率换时间效率,代码体会很庞大

在函数前加上inline
定义声明方法:

inline void output(a,b);

inline void output(a,b){
    cout << a << b << endl;
}

函数的执行中需要经历压栈和出栈、值拷贝等等,内联函数的作用类似宏函数,但是内联函数由编译器展开,能够进行语法检查和复杂传参(宏函数由预处理器处理,没有语法检查)

for(int i = 0 ; i < 1000; i++){
    .....
    function(a,b);
    .....
}

如果function是内联函数,编译器会直接将function在调用处展开,类似宏定义,而不进行压栈和出栈

注意:内联函数在声明和定义时都需要加inline,如果定义不加,则还是普通函数

参数:

默认参数

设置默认参数,允许空参调用。

void func(int a = 666){
    cout << "a=" << a << endl;
}
void main(){
    func();
}

多个参数设置规则:
有默认值的参数必须在形参列表末尾

void func(int b,int a = 666){
    cout << "a=" << a << endl;
}

占位参数

顾名思义,只占位置,不会在函数中使用,调用时必须填上这个位置,用于预留位置

int func(int a,int){
    cout << a << endl;
}

重载:

c++支持重载
在函数名相同的前提下

  1. 参数个数不同
  2. 参数类型不同
  3. 参数顺序不同

均可构成重载

注:重载不关心返回值,同名同参数列表,不同类型返回值的函数之间,不能重载

重载和参数细则:

  1. 需要重载时不要加默认参数,产生调用歧义就报错
    例如:

    int func(int a,int b,int c = 1){
     .......
    }
    
    int func(int a, int b){
     .......
    }
    

    在用func(1,2)调用时不能确定到底是以上哪个函数

  2. 需要重载时可以用占位参数,不会产生调用歧义

  3. 参数个数相同优先调用,通过隐式转换能匹配类型的
    例如:调用fa(1)

    void fa(double a) {
     cout << "double";
    }
    void fa(int a) {
     cout << "int";
    }
    

    fa(1);会优先调用参数为int型的fa,如果没有第二个fa,则调用第一个

函数指针:

函数指针的作用,主要是能够把函数作为参数进行传递

定义函数类型

  1. 定义方法:
typedef int(MY_FUNC)(int,double)

解释:
typedef [返回值类型]([自定义函数名称])([形参类型列表])

定义函数指针

定义方法(最常用):

MY_FUNC* fp = NULL;
fp = func;
fp(10,20);
.....

定义指向某函数类型的指针类型

就是把返回类型改一下,有两种写法。(*符号位置不同)

第一种:

typedef int*(MY_FUNC_P)(int,int);

第二种:

typedef int(*MY_FUNC_P)(int,int);

函数指针调用方法:

MY_FUNC_P fp = NULL;
fp = func;
fp(1,1);

定义指向函数类型的指针类型不常用,因隐藏了*符号容易忘记这是指针

不用typedef定义函数指针

示例:

int(*fp)(int,int) = NULL;
fp = func;

重载和函数指针交互

  1. 在给函数指针赋值时,就进行函数重载匹配

  2. 函数指针只会指向匹配的参数列表的函数

  3. 一旦匹配后,通过指针调用时就不再为重载进行匹配

swap函数:

在很多算法中,特别是排序算法,经常需要交换元素的位置,所以std::swap在实践中非常有用。定义如下:

template <class T>
void swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}

使用方法为swap(a,b)
这里的T是一个模板参数,表示可以是任何类型。因此,可以使用std::swap来交换整数、浮点数、字符串、自定义对象等的值。

类和对象:

结构体就是一个类默认访问权限是public,里面可以写函数或设置访问权限

访问权限(公私有)

  1. public和private都与Java一样

  2. protected与Java不同

    C++:protected 仅可被子类拥有该成员的类访问

    Java:protected不仅可以被子类、拥有该成员的类访问,同一个包内的类也可以访问

  3. 不写访问权限修饰符,默认是private

对象作参数

  1. 一般传的都是对象的引用,方便的修改或调用

  2. 有对象做参数的函数格式

void eat(class Dog &dog);

多文件中写类

c++写类的标准是:
.h头文件 + .cpp源文件

头文件中写声明,源文件中写定义,格式如下。
Circle.h头文件:(不要忘记#pragma once

#pragma once
class Circle
{
private:
	int r;
	double c;
public:
	Circle(int a);
	int getR();
	double getC();
};

Circle.cpp源文件:

#include "Circle.h"

int Circle::getR() {
	return r;
}
double Circle::getC() {
	return c;
}
Circle::Circle(int r) {
	this->r = r;
}

需要创建该类的对象时,需要引入Circle头文件include "Circle.h"

标签:const,函数,int,void,笔记,学习,引用,指针,C++
From: https://www.cnblogs.com/cupCheng/p/17680877.html

相关文章

  • C++中虚继承时的构造函数
    在虚继承中,虚基类是由最终的派生类初始化的,换句话说,最终派生类的构造函数必须要调用虚基类的构造函数。对最终的派生类来说,虚基类是间接基类,而不是直接基类。这跟普通继承不同,在普通继承中,派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的。下面我们以菱形继承为例来......
  • STL学习笔记
    迭代器迭代器(iterator)是一种抽象的设计概念,现实程序语言中并没有直接对应于这个概念的实物。在<>一书中提供了23中设计模式的完整描述,其中iterator模式定义如下:提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。迭代器案例#include<ios......
  • C++语言学习09
    STL标准模版库STL是StandardTemplateLibrary的缩写,中文名标准模版库,由惠普实验室提供(使用C++模板语言封装的常用的数据结构与算法)STL中有六大组件:算法:以函数模板形式实现的常用算法,例如:swap\max\min\find\sort容器:以类模板的形式实现的常用的数据结构,例如:vector\list\arra......
  • vi/vim笔记
    一些vi配置:#光标所在行显示下划线:setcursorline#对应关闭为setnocursorline#高亮显示光标所在列setcursorcolumn#对应关闭为setnocursorcolumn#tab键字符替换为4个空格setts=4setsw=4setexpandtabsetautoindent#复制粘贴模式setpasteif&diffco......
  • 1 C++基础问题总结
    C++基础1C和C++有什么区别?C++是面向对象,C面向过程C++引入new/delete运算符,取代了C中的malloc/free库函数;C++有引用的概念,C没有C++有类的概念,C没有C++有函数重载,C没有2a和&a有什么区别?比如inta[10];int(*p)[10]=&aa是数组名,是数组首元素地址,+1表示地址值加上一......
  • Markdown学习
    Markdown学习标题三级标题四级标题HelloWorld!HelloWorld!HelloWorld!HelloWorld! 引用选择狂神说JAVA,走向人生巅峰 分割线 图片 超链接点击跳转到狂神博客  列表13 ABC 表格名字性别生日张三男1997.1.1......
  • C++中模块(DLL)对外暴露接口的几种方式
    函数导出:通过在函数前面加上导出修饰符(如__declspec(dllexport))来导出函数。优点是简单易用,缺点是无法避免函数名冲突,且需要手动导出每个函数。.def文件:通过定义一个.def文件,在其中指定要导出的函数名和入口点。优点是可以一次性导出多个函数,缺点是需要额外的.def文件,且与代码分......
  • Paddle图神经网络训练-PGLBox代码阅读笔记
    图存储部分paddle/fluid/framework/fleet/heter_psgraph_gpu_wrapper.hGPU图主入口graph_gpu_ps_table.hGPU图的主要存储结构,neighbor采样等都在这里完成gpu_graph_node.h节点,边,邻居等数据结构定义paddle/fluid/distributed/ps/table/common_graph_tabl......
  • c++/c中关于头文件的探索
    //Fin.h#ifndefFIN_H#defineFIN_Hintadd(inta,intb);#endif//Fin.cpp#include"Fin.h"intadd(inta,intb){returna+b;}//Test1.cpp#include<iostream>#include"Fin.h"//包含Fin.h来调用函数intmain(){......
  • 《Java编程思想第四版》学习笔记23
    在Inning中,可以看到无论构建器还是event()方法都指出自己会“掷”出一个违例,但它们实际上没有那样做。这是合法的,因为它允许我们强迫用户捕获可能在覆盖过的event()版本里添加的任何违例。同样的道理也适用于abstract方法,就象在atBat()里展示的那样。“interfaceStorm”非......