• C/C++语言知识


    C/C++语言知识



    变量和函数的存储类别

    自动变量具有块作用域、无链接、自动存储期。它们是局部变量,属于其定义所在块(通常指函数)私有。寄存器变量的属性和自动变量相同,但是编译器会使用更快的内存或寄存器储存它们。不能获取寄存器变量的地址。

    具有静态存储期的变量可以具有外部链接、内部链接或无链接。在一个文件中,函数外部声明的变量是外部变量,具有文件作用域、外部链接和静态存储期。如果在这种声明前面加上关键字static,那么其声明的变量具有文件作用域、内部链接和静态存储期。如果在函数中用 static 声明一个变量,则该变量具有块作用域、无链接、静态存储期。

    具有自动存储期的变量,程序在进入该变量的声明所在块时才为其分配内存,在退出该块时释放之前分配的内存。如果未初始化,自动变量中是垃圾值。程序在编译时为具有静态存储期的变量分配内存,并在程序的运行过程中一直保留这块内存。如果未初始化,这样的变量会被设置为0,包括数组和结构体成员。

    具有块作用域的变量是局部的,属于包含该声明的块私有。具有文件作用域的变量对文件(或翻译单元)中位于其声明后面的所有函数可见。具有外部链接的文件作用域变量,可用于该程序的其他翻译单元。具有内部链接的文件作用域变量,只能用于其声明所在的文件内。

    示例

    将下面两个文件(main1.cpp 和 main2.cpp)一起编译即可。

    // main1.cpp
    #include 
    #pragma warning(disable:4996)
    
    //外部函数原型如下,默认采用 extern 关键字,可以被其他文件访问
    void report_count();
    void accumulate(int k); //函数定义在其他地方
    static void fun();// static 表示该函数为此文件私有,不可被外部访问。
    
    int count; // 文件作用域,外部链接,全局变量默认初始化为 0
    int main(void)
    {
    	int value = 2; // 自动变量  初始化为随机值
    	register int i; // 寄存器变量(仅仅是建议编译器采用寄存器来存储)  初始化为随机值  不能对其取地址 
    
    	for (i = value; i >= 0; i--) 
    		accumulate(i);
    
    	report_count();
    	return 0;
    }
    void report_count()
    {
    	printf("%d\n", count);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    // main2.cpp
    #include 
    
    extern int count; // 引用式声明,外部链接
    static int total ; // 静态定义,内部链接
    void accumulate(int k); // 函数原型
    void accumulate(int k)// k 具有块作用域,无链接
    {
    	static int subtotal ; // 静态,无链接
    		if (k <= 0)
    			subtotal = 0;
    		else{
    			subtotal += k;
    			total += k;
    		}
    		count++;
    		printf("%d %d\n", subtotal, total);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    函数参数和返回值

    个人认为:函数参数均采用传值的方式,意味着传递的是副本(引用可等同于指针);函数的返回值,采用的也是传值的方式,一般情况下,会将函数的返回值赋值给某一个变量。

    采用传指针/引用,和返回指针/引用 在传递/返回一个结构体这样的大对象时,避免了传递时生成该结构体副本/返回时生成该结构体副本。效率更高!
    注意:不能返回一个局部变量的指针/引用,因为函数执行完毕之后,该内存不再存在。所以一般采用指针/引用都是应用在返回指向 new 生成的内存,

    #ifndef 和 #pragma once

    条件编译1条件编译2extern ‘C’

    一、const 关键字

    // cv-限定符
    // 指的是 const 和 volatile
    // 而 mutable 修饰符和 const 有关
    struct MyStruct
    {
    	// mutable 修饰符 表示即使该结构体的变量被声明为 const,a 的值依然可以被修改。
    	mutable int a; 
    	double b;
    };
    int main() {
    
    	const MyStruct mys = { 4,5.6 };
    	mys.a++; // success
    	mys.b += 0.1;// error
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    a、const修饰常量,表示其值不可改变,且必须在定义的时候必须初始化

    b、const修饰函数参数,表示该参数的值在函数体内不能被修改

    c、const修饰函数返回值

    a、修饰普通返回值,如 const int f();由于该返回值是一个临时变量,随着函数调用结束后,其声明周期也就结束了,所以没有意义。

    b、修饰指针,如

    const int* f(); //函数  表示该返回值所指向的值不能被修改
    int* res=f();  // 错误
    const int* res=f(); //正确
    
    • 1
    • 2
    • 3

    c、修饰引用,如

    const int& f1(int& a) { return a; }
    int& f(int& a) { return a; }
    int main() {
    	int a = 3;
    	f1() = 89;//错误 函数调用表达式不能作为左值。
    	f(a) = 90;//正确
    	cout << a << endl; // 90
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    d、const 和 指针

    	int a = 3, b = 4;
    	int* p = &a;
    	p = &b;
    	*p = 20;
    
    	const int* p1 = &a;  //修饰指针所指向的值,此指针可以修改,但是此指针指向的值不能被修改
    	p1 = &b;
    	//*p1 = 20;
    
    	int const* p2 = &a;  // 同上  (可以认为此二者的const均修饰 *)
    	p2 = &a;
    	//*p2 = 20;
    	
    	int* const p3 = &a;  //仅修饰指针,指针不能修改,但是指针指向的值可以被修改
    	//p3 = &b;
    	*p3 = 20;
    
    	const int* const p4 = &a; // 均修饰,指针不能修改,且其指向的值也不能被修改
    	//p4 = &b;
    	//*p4 = 20;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    e、C const 和 C++ const

    	const int len = 9;
    	int array[len]; // cpp 允许,而 c 不允许
    
    • 1
    • 2

    二、printf

    	
    	printf("%d\n");//打印出内存中的随机值
    	int a=90;
    	int b = printf("%d\n", a);
    	printf("%d\n", b); // b为3  它为printf打印出字符的个数!  负值表示出错。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    三、数组、字符串

    a、字符数组:存储 char 的数组;字符串:一系列 char 和 结尾的 ‘\0’ 构成

    b、一维数组

    	char a[20];
    	printf("%d\n", a[0]); // 正确,打印出随机值
    	int b;
    	printf("%d\n", b); // 运行时错误,b 未初始化
    	
    	//和普通变量一样,应该在声明时来初始化 const 数据,因为一旦声明为const,便不能再给它赋值。
    	const int m[4]={1,2,3,4};
    
    	int n[4]={1};  // n 中值为: 1 0 0 0
    	// 由于数组一旦有部分元素被初始化,其余元素就会被初始化为 0,所以:对一个数组初始化全为 0 的方式如下
    	int nn[4]={0};
    
    	int mm[]={3,4};// 数组大小为2;
    	int len=sizeof(mm)/sizeof(int);//  或者:sizeof(mm)/sizeof(mm[0]);
    
    	#define N 5  // c 
    	constexpr auto N1 = 5; // cpp 常量表达式
    	int main() {
    		int i[N1]; // 初始化数组大小
    		int ii[3 * 7]; // 初始化数组大小
    		const int j = 8;
    		int k[j];// const 只能在cpp中 初始化数组大小
    		return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    c、二维数组

    	int sq[2][3] = { 5,6,7,8 }; //不带括号的从第一行顺序初始化
    	/* 5 6 7
    	*  8 0 0
    	*/
    	int s[2][3] = { {5,6},{7,8} };
    	/* 5 6 0
    	*  7 8 0
    	*/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    四、数组和指针

    a、一维数组

    	int a[7] = { 5,6,7,8 }; 
    	int* p = &a[0];
    	int* p1 = a;
    	printf("%d\n", *p);
    	printf("%d\n", *(p+1)); //指针加1,指针的值递增它所指向类型的大小。	
    	printf("%d\n", p[1]);
    
    	p + 2 == &p[2]; // 相同的地址
    	*(p + 2) == p[2]; // 相同的值
    
    	*(p + 2); // p 第3个元素的值
    	*p + 2; // p 第1个元素的值加2    * 的运算符优先级比算术运算符要高。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    a.a、 常见运算符优先级

    Little_ant_

    b、二维数组

    	int zippo[3][2] = { 0,1,2,3,4,5 };
    	printf("%d\n", zippo[2][1]);
    	printf("%d\n", *(*(zippo + 2) + 1));
    	// *(zippo+2)  zippo[2][0] 的地址
    	// *(zippo+2)+1  zippo[2][1] 的地址
    	//*(*(zippo+2)+1)  zippo[2][1] 的值
    	
    	int(* p)[2] = zippo; // 指向二维数组的指针 采用的括号的原因是因为 [] 比 * 的优先级高。
    	printf("%d\n", **p);  // 等同于 zippo[0][0]
    	printf("%d\n", *(*(p + 2)+1)); // 等同于 zippo[2][1]
    
    	//int* p[2];  //此时 p 是一个存放 int* 的一维数组,数组长度为 2.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    b.b、数组作为函数参数

    // int array[8][4];  //将该数组作为函数参数
    int fun1(int(*p)[4]);
    int fun2(int p[][4]);
    int fun1(int p[][]);// 错误的传参
    
    int fun3(int* p);
    int fun4(int p[]);
    
    //一般而言,声明一个指向N维数组的指针时,只能省略最左边方括号中的值:
    //第1对方括号只用于表明这是一个指针,而其他的方括号则用于描述指针所指向数据对象的类型。
    //下面的 ar 表示其指向一个12×20×30的int数组。
    
    int fun5(int ar[][12][20][30]);
    int fun6(int(*ar)[12][20][30]);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    c、野指针

    	//一、未初始化的指针
    	int* pp;
    	printf("%p\n", pp); //报错 pp未初始化
    	printf("%d\n", *pp); // 报错 pp未初始化
    	*pp=9; //报错 pp未初始化
    	/*
    	pp未被初始化,其值是一个随机值,所以不知道 9 将储存在何处。这可能不会出什么错,也可能会擦写数据或代码,或者导致程序崩溃。
    	创建一个指针时,系统只分配了储存指针本身的内存,并未分配储存数据的内存。
    
    	要么设置它的值为NULL,要么让它指向已有变量/数组的地址,要么让它指向 malloc/new 分配的内存。
    */
    	
    	//二、指针被释放之后,仅仅释放掉其指向的内存,指针本身并未被释放掉。
    	int* uu = new int;
    	delete uu;
    	uu = NULL;  //建议 delete/free 之后,令指针值为 NULL
    
    
    int* fun() {
    	int a = 9;
    	return &a;
    }
    int main() {
    	//三、指针操作超出了变量的作用范围,
    	int* res = fun(); // 此时 a 所在的内存已经被释放掉了。
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    d、通用指针 void*

    // 通用指针 void* : 可指向任何指针,但解引用时需要先进行转换。
    	int a = 3;
    	int* p;
    	void* v = &a;
    	printf("%d\n", *v);// 报错,不知道 v 指向内容的长度。
    	p = (int*)v;
    	printf("%d\n", *p);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    五、简单的类型转换

    Java 变量

    	double dd = 9.87;
    	int iii = dd;//错误
    	int i=(int)dd; //正确
    
    • 1
    • 2
    • 3

    C 变量

    	// 隐式类型转换
    	double dd = 9.87;
    	int iii = dd;
    
    • 1
    • 2
    • 3

    C 指针

    	// 指针只能显式类型转换
    	double* a = new double(8.98);
    	int* b = a; //报错,无法转换
    	int* b = (int*)a;
    	delete a;
    	a=NULL
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    1、c 语言: 字符串和其他类型的转换

    #include
    #pragma warning(disable:4996)
    
    using namespace std;
    int main() {
    	// c 语言: 字符串和其他类型的转换
    	char* a = new char[30];
    	double b = 8.2385;
    	sprintf(a, "%.2lf", b); // printf中一般可以用%f代替%lf
    	//a 此时为一个字符串。
    	cout << a << endl;
    
    	double c;
    	sscanf(a, "%lf", &c); // scanf中%lf与%f是严格区分的
    	cout << c << endl;
    	delete[] a;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2、cpp 初始化

    	//数组
    	//列表初始化:
    	int a[]{ 2,3,4 }; // 2 3 4
    	int b[6]{ 2,3,4 };// 2 3 4 0 0 0
    	int d[3]{};// 0 0 0
    
    	int c[3] = {};// 等同于 c 中的 int c[3]={0};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    	//结构体
    	struct MyStruct { int a; double b; };
    	MyStruct mys{ 4,5.6 };
    	MyStruct mm = { 4,5.7 };
    
    • 1
    • 2
    • 3
    • 4
    	//字符串初始化
    	//char* a = "jjjj";//错误
    	const char* a = "jjjj";//正确  a 是一个字符串常量
    	char b[] = "uuuuu";  // b 是一个字符串
    	b[0] = 'o';
    	cout<<sizeof(b)<<endl; // 6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    	// new 初始化
    	int* a = new int(9); //单值变量
    
    	// 数组/结构体的列表初始化
    	struct MyStruct { int a; double b; };
    	MyStruct* mys = new MyStruct{1, 3.4};//结构体
    	int* b = new int[5] {};
    	int* c = new int[] {2, 3};
    	int* d = new int[4]{ 1 };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3、函数

    a、默认的函数参数

    int fun(int node=4){
    	return node;
    }
    int main() {
    	int res = fun();
    	cout << res << endl;  // 4
    }
    //参数有多个的时候,只能从右往左设置参数的默认值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    b、函数模板

    #include
    #pragma warning(disable:4996)
    using namespace std;
    
    //函数模板:
    template<typename T>
    void swap1(T& a, T& b) {
    	T tmp = a;
    	a = b;
    	b = tmp;
    }
    int main() {
    	int a = 8, b = 9;
    	double aa = 8.1, bb = 9.1;
    	swap1(a, b); // 9 8
    	swap1(aa, bb);// 9.1 8.1
    
    	// std::swap 函数
    	swap(a, b); // 8 9
    	swap(aa, bb);// 8.1 9.1
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    4、结构体,C字符串、string赋值

    struct a {
    	char c[20];
    	int i;
    	double d;
    };
    int main() {
    	a aa = { "kjisf",9,2.34 };
    	a bbb = aa; // 结构体可以直接赋值
    	cout << bbb.c << " " << bbb.i << " " << bbb.d << endl;
    
    	string s = "lkjsf";
    	string ss = s;// string 可以直接赋值(重写赋值操作符)
    
    	// C字符串不可直接赋值,需要采用 strcpy 函数 或者 for循环依次赋值
    	char tmp[6];
    	strcpy(tmp, "12345");
    	char* b = new char[6];
    	strcpy(b, "12345");
    	delete[] b;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    5、类

    类中的private: 可以省略,这是类默认的访问策略。

    像一些简单的函数定义(方法体),如Java中的getter、setter,可以直接写在类声明文件中,位于类声明中的函数定义自动成为内联函数。
    或者在类定义文件中,与其他函数的定义放在一起,此时需要在函数定义之前显式添加 inline 关键字,来表明其是一个内联函数。

    a、初始化

    类初始化如下:

    class C {
    	int a, b;
    	double c;
    public:
    	C(int a, int b, double c) {
    		this->a = a;
    		this->b = b;
    		this->c = c;
    	}
    };
    int main() {
    	C c = C(2, 3, 4.5);// 显式调用构造函数
    	C cc(1, 2, 8.9);// 隐式调用构造函数
    	
    	C c1 = C{ 2, 3, 4.5 }; // 列表初始化
    	C cc1{1, 2, 8.9};
    	
    	C* ccc = new C{ 1,2,3.4 };
    	C* cccc = new C(3, 4, 5.6);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    和Java一样,不提供自己的构造函数,程序只能使用默认的构造函数;如果提供了自己的构造函数,而且还想使用默认构造函数的话,就需要显式声明了。

    class C {
    	int a, b;
    	double c;
    public:
    	C(int a, int b, double c) {
    		this->a = a;
    		this->b = b;
    		this->c = c;
    	}
    	C() { // 实现默认的构造函数,提供隐式初始值
    		a = b = 0;
    		c = 0.0;
    	}
    	~C(){} // 默认的析构函数,如果构造函数采用了new,那么在析构函数中需要采用delete。
    };
    int main() {
    	C c = C(); //显式调用 默认构造函数
    	// 如果默认构造函数未作初始化,则 c 中的类成员为随机值。
    	C cc; //隐式调用
    	C* c1 = new C();
    	C* cc1 = new C;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    b、赋值

    int main() {
    	C c = C();  //初始化 (可能会创建临时对象)
    	c=C(); //赋值,一定会创建临时对象,创建一个临时对象,然后赋值给变量 c,最后调用析构函数
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    c、const 成员

    a、const 成员函数

    public:
    	void show() const; // 保证调用show方法时,不会修改调用者的成员变量。
    
    void C::show() const{
    	cout<<"haha"<<endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    b、const 成员变量

    class C {
    private:
    	const int a;
    public:
    	C(int aa) :a(aa) {} 只能采用列表初始化来进行
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5、string类

    int main() {
    	string s = "jlsjl";
    	string s1("kskfj");
    	string ss = s;// 赋值操作
    	string sss = ss + s;// 拼接
    
    	string ssss;
    	getline(cin, ssss);// 从标准输入接收一行输入
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  • 相关阅读:
    Centos7 Shell编程之正则表达式、文本处理工具
    加入数(insdata)
    Fiber的理解
    Restyle起来!
    嵌入式硬件库的基本操作方式与分析
    Python学习----Day08
    Vue.js核心技术解析与uni-app跨平台实战开发学习笔记 第10章 Vuex状态管理 10.1 Vuex基础应用
    计算机毕业设计Python+Django的银行取号排队系统
    TCP 和 UDP 可以同时绑定相同的端口吗?
    Linux基础知识与实操-篇七:用户身份切换与特殊控制
  • 原文地址:https://blog.csdn.net/Little_ant_/article/details/126384485