GUI框架 | 开发语言 | 美观度 | 移植性 | 亮点 |
miniGUI | C/C++ | 一般 | 一般 | 国产 资源占用少 操作硬件底层方便 |
QT | C/C++ | 很好 | 很好 | 开源 资源占用较少 操作硬件底层方便 |
Android | java | 很好 | 差 | 用户基数大 大公司可靠较好 |
.NET | C# | 很好 | 较好 | 用户基数大 大公司可靠较好 |
从C到C++
语法的升级
引用 :
在C++中,引用(Reference)是一种类似于指针的数据类型,用于提供对其他变量的别名。引用被视为某个变量的别名,对引用的操作实际上是对原始变量的操作。
引用的概念允许我们对变量进行简洁的操作,而无需直接操作指针。引用的声明使用&符号,例如:
int a = 10;
int& b = a; // 声明一个整型引用b,它是a的别名
b = 20; // 修改b也会修改a的值
在上面的例子中,变量b是变量a的引用。修改b的值会影响到a的值,反之亦然。这是因为b和a实际上是同一块内存地址的别名。
引用的特点和用途包括:
1. 别名:引用提供了一个变量的别名,可以通过引用访问和操作变量。
2. 传递参数:通过引用作为函数参数,可以实现传递参数的效果,避免了复制变量的开销,并可以修改原始变量的值。
3. 返回值:函数可以返回引用作为结果,从而允许对函数的返回值进行修改。
4. 避免空指针:与指针不同,引用必须在声明时初始化,并且不允许为空,因此可以避免访问空指针的问题。
需要注意的是,引用与指针是不同的。引用在声明时必须初始化,并且不能被重新赋值为引用其他变量,而指针可以在任何时候指向不同的对象。同时,引用不占用额外的内存空间,而指针需要存储内存地址。
void swp(int &a, int &b);
#include <stdio.h>
int main()
{
int a = 100;
int &b = a;
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("addr: a = %p\n", &a);
printf("addr: b = %p\n", &b);
}
#include <stdio.h>
/*
int swap(int a, int b)
{
a ^= b;
b ^= a;
a ^= b;
}
*/
/*
void swap(int *p, int *q)
{
*p ^= *q;
*q ^= *p;
*p ^= *q;
}
*/
int swap(int &a, int &b)
{
a ^= b;
b ^= a;
a ^= b;
}
int main()
{
int a = 100;
int b = 10;
printf("a = %d, b = %d\n", a, b);
// swap(&a, &b);
swap(a, b);
printf("a = %d, b = %d\n", a, b);
}
默认参数 :
void debug(const char *ptr=“----------”);
C++中的默认参数是在函数声明中为参数提供默认值的一种功能。默认参数允许调用函数时不必为每个参数都提供值,而是使用默认值作为替代。
在函数声明和定义中,可以通过在参数列表中为参数指定默认值来定义默认参数。例如:
void printMessage(const std::string& message = "Hello, World!") {
std::cout << message << std::endl;
}
在上面的例子中,printMessage函数有一个参数message,并将其默认值设置为"Hello, World!"。这意味着当调用printMessage函数时,如果不提供任何参数,将使用默认值输出"Hello, World!"。
使用默认参数时,有几个要点需要注意:
1. 默认参数只能在函数的声明中指定,而不是在函数的定义中。这是因为默认参数的值在编译时就已经确定了。
2. 如果函数有多个参数,那么只能在参数列表的尾部提供默认参数。也就是说,默认参数后面的参数都必须有默认值。
3. 默认参数只需要在函数声明中指定一次。如果在函数定义中再次指定,默认参数的值将被忽略。
4. 调用函数时,可以选择省略默认参数,此时将使用默认值。也可以显式地提供不同的值来覆盖默认参数。
默认参数提供了更灵活和便捷的函数调用方式,使得函数在不同的调用场景中更加适用。
#include <stdio.h>
//void sortarr(int *arr, int len, int flag=UP);
void debug(const char *ptr = "---------------")
{
printf("%s\n", ptr);
}
int main()
{
debug();
debug();
debug();
debug();
debug();
debug();
debug("hello");
debug("world");
}
函数重载 :
int cmp(int data1, int data2 ); int cmp(const char *str1, const char *str2);
C++函数重载是指在同一作用域内,可以有多个同名函数,但参数列表必须不同(参数个数或者参数类型不同),以便根据不同的参数列表选择调用不同的函数。
函数重载的优点是可以提高代码的可读性和可维护性,同时可以提供更灵活的接口。例如,可以定义多个同名的构造函数或运算符重载函数,以便根据不同的参数类型来创建对象或执行不同的操作。
以下是函数重载的示例:
#include <iostream>
// 函数重载示例
void print(int num) {
std::cout << "整数: " << num << std::endl;
}
void print(double num) {
std::cout << "浮点数: " << num << std::endl;
}
void print(const char* str) {
std::cout << "字符串: " << str << std::endl;
}
int main() {
print(10); // 调用第一个print函数
print(3.14); // 调用第二个print函数
print("Hello, World!"); // 调用第三个print函数
return 0;
}
输出结果为:
整数: 10
浮点数: 3.14
字符串: Hello, World!
在上面的示例中,定义了三个同名的print函数,但参数类型分别为int、double和const char*,根据不同的参数类型选择调用不同的print函数。
#include <stdio.h>
#include <string.h>
/*
int intcmp(int a, int b)
{
return a-b;
}
int scmp(const char *str1, const char *str2)
{
return strcmp(str1, str2);
}
*/
int cmp(int a, int b)
{
return a-b;
}
int cmp(const char *str1, const char *str2)
{
return strcmp(str1, str2);
}
int main()
{
printf("%d\n", cmp(1, 2));
printf("%d\n", cmp("aaaaaa", "bbbb"));
}
堆内存:
malloc(), free() new, delete
#include <stdio.h>
#include <malloc.h>
#include <string.h>
int main()
{
/*
char *p = (char *)malloc(10);
strcpy(p, "hello");
printf("p: %s\n", p);
free(p);
*/
int *intp = new int;//(int*)malloc(sizeof(int));
*intp = 100;
printf("*intp = %d\n", *intp);
delete intp;
/
char *p = new char[10];
strcpy(p, "hello");
printf("p: %s\n", p);
delete [] p;
}
概念的升级
C语言封装
//arr.c
#include "arr.h"
#include <stdio.h>
void init(ARR *arr)
{
arr->tail = 0;
}
void addtail(ARR *arr, int data)
{
arr->data[arr->tail++] = data;
}
void show(ARR *arr)
{
int i = 0;
for(;i<arr->tail; i++)
printf("%d, ", arr->data[i]);
printf("\n");
}
//arr.h
#ifndef _ARR_
#define _ARR_
typedef struct arr{
int data[100];
int tail;
}ARR;
void init(ARR *arr);
void addtail(ARR *arr, int data);
void show(ARR *arr);
#endif
//main.c
#include "arr.h"
int main()
{
ARR arr;
init(&arr);
int n = 10;
while(n--)
addtail(&arr, n);
show(&arr);
ARR arr1;
init(&arr);
int i = 0;
for(;i<10; i++)
addtail(&arr1, i);
show(&arr1);
}
C语言实现封装
//arr.c
#include "arr.h"
#include <stdio.h>
static void addtail(ARR *arr, int data)
{
arr->data[arr->tail++] = data;
}
static void show(ARR *arr)
{
int i = 0;
for(;i<arr->tail; i++)
printf("%d, ", arr->data[i]);
printf("\n");
}
void init(ARR *arr)
{
arr->tail = 0;
arr->addtail = addtail;
arr->show = show;
}
//arr.h
#ifndef _ARR_
#define _ARR_
typedef struct arr{
int data[100];
int tail;
void (*addtail)(struct arr *arr, int data);
void (*show)(struct arr *arr);
}ARR;
void init(struct arr *arr);
#endif
//main.c
#include "arr.h"
int main()
{
ARR arr;
init(&arr);
int n = 10;
while(n--)
arr.addtail(&arr, n);
arr.show(&arr);
// arr.tail = 0;
arr.show(&arr);
}
类的申明
class 类名
{
private:
私有的数据和成员函数;
public:
公用的数据和成员函数;
protected:
保护的数据和成员函数
};
C++中类和结构体区别:
在C++中,类(Class)和结构体(Struct)在语法上是非常相似的,它们都可以用来定义自定义的数据类型。然而,类和结构体之间有一些区别:
1. 默认访问权限:
- 类的成员默认为私有(private)权限,需要使用公有(public)成员函数来访问。
- 结构体的成员默认为公有(public)权限,可以直接访问。
2. 继承:
- 类可以继承其他类,支持面向对象的继承特性。
- 结构体默认为公有继承,并且不支持多重继承。
3. 默认构造函数:
- 类可以自定义默认构造函数,也可以使用编译器提供的默认构造函数。
- 结构体如果没有显式定义构造函数,编译器会自动生成一个默认构造函数。
4. 成员初始化:
- 类的成员变量可以在构造函数中进行初始化。
- 结构体的成员变量默认不进行初始化,需要手动进行初始化。
在实际使用中,一般情况下,如果数据类型仅包含数据成员,并且没有需要进行封装的操作,可以使用结构体。如果需要封装数据成员,并且需要定义成员函数来访问和操作数据成员,可以使用类。
然而,除了上述的差异之外,类和结构体在语法上是相似的,可以具有相同的成员函数、访问修饰符、静态成员等特性,并且可以通过指针或引用进行访问。因此,在实际使用中,可以灵活选择使用类或结构体来定义自定义的数据类型,以满足具体需求。
类的成员函数(类内/类外实现)
构造函数(类的初始化,创建类时自动调用;初始化表,this指针)
默认构造函数 A();
拷贝构造函数 A(const A &x); //深浅拷贝问题
赋值构造函数 operator=?;
析构函数(类的解构,销毁类时自动调用)
~A();
普通成员函数
构造函数
在C++中,构造函数是一种特殊的成员函数,用于在创建类的对象时进行初始化操作。构造函数的名称与类的名称相同,没有返回类型(包括void)。
构造函数有以下几个特点:
- 构造函数在对象创建时自动调用,无需手动调用。
- 构造函数可以有参数,也可以没有参数。有参数的构造函数称为带参构造函数,用于初始化对象的成员变量。
- 如果没有定义任何构造函数,编译器会默认生成一个无参构造函数,该构造函数不做任何操作。
- 如果定义了带参构造函数,编译器不会再生成默认的无参构造函数,需要手动定义。
- 一个类可以有多个构造函数,它们之间通过参数个数、参数类型或参数顺序进行区分,称为构造函数的重载。
- 构造函数可以被继承,派生类的构造函数可以调用基类的构造函数来初始化基类的成员变量。
以下是一个示例代码,展示了一个简单的类和它的构造函数的定义和使用:
class Person {
private:
std::string name;
int age;
public:
// 无参构造函数
Person() {
name = "";
age = 0;
}
// 带参构造函数
Person(std::string n, int a) {
name = n;
age = a;
}
// 成员函数
void display() {
std::cout << "Name: " << name << ", Age: " << age << std::endl;
}
};
int main() {
// 使用无参构造函数创建对象
Person p1;
p1.display(); // Output: Name: , Age: 0
// 使用带参构造函数创建对象
Person p2("Alice", 25);
p2.display(); // Output: Name: Alice, Age: 25
return 0;
}
在上述示例中,我们定义了一个名为Person
的类,它有一个私有成员变量name
和age
,以及一个公有成员函数display
用于显示信息。类中定义了两个构造函数,一个是无参构造函数,一个是带参构造函数。我们在main
函数中分别使用这两个构造函数创建了两个对象,并调用了display
函数显示信息。
析构函数
在C++中,析构函数是一种特殊的成员函数,用于在对象销毁时执行清理操作。析构函数的名称与类的名称相同,前面加上一个波浪号(~)作为前缀,没有返回类型(包括void)。
析构函数有以下几个特点:
- 析构函数在对象销毁时自动调用,无需手动调用。
- 析构函数不接受任何参数,只有一个实例。
- 如果没有定义任何析构函数,编译器会默认生成一个空的析构函数,不做任何操作。
- 如果定义了析构函数,编译器将不再生成默认的析构函数。
以下是一个示例代码,展示了一个简单的类和它的析构函数的定义和使用:
#include <iostream>
#include <string>
class Person {
private:
std::string name;
public:
// 构造函数
Person(std::string n) {
name = n;
}
// 析构函数
~Person() {
std::cout << "Destroying " << name << std::endl;
}
};
int main() {
// 创建对象
Person p1("Alice");
Person p2("Bob");
return 0;
}
在上述示例中,我们定义了一个名为Person
的类,它有一个私有成员变量name
,以及一个构造函数和一个析构函数。在main
函数中创建了两个对象p1
和p2
,当这两个对象超出其作用域时,会自动调用析构函数进行清理操作。在析构函数中,我们简单地输出了被销毁的对象的名称。
输出结果如下:
Destroying Bob
Destroying Alice
可以看到,对象销毁时,析构函数会被自动调用,执行相应的清理操作。
用C++实现数组末尾插入数据
//arr.c
#include "arr.h"
#include <stdio.h>
void ARR::addtail(int data)
{
this->data[tail++] = data;//类对象自己的指针用this
}
void ARR::show(void)
{
int i = 0;
for (;i < tail; i++)
printf("%d, ", data[i]);
printf("\n");
}
/*
void init(ARR *arr)
{
arr->tail = 0;
arr->addtail = addtail;
arr->show = show;
}
*/
//arr.h
#ifndef _ARR_
#define _ARR_
#if 0
typedef struct arr
{
int data[100];
int tail;
void (*addtail)(struct arr* arr, int data);
void (*show)(struct arr* arr);
}ARR;
void init(struct arr* arr);
#endif
class ARR
{
public:
ARR() :tail(0)//初始化表初始化法
{
// tail = 0;
}
void addtail(int data);
void show(void);
friend void rev(ARR& arr);
private:
int data[100];
int tail;
};
#endif
//main.c
#include "arr.h"
void rev(ARR &arr)
{
int i = 0;
for(;i<arr.tail/2; i++)
{
int tem = arr.data[i];
arr.data[i] = arr.data[arr.tail-i-1];
arr.data[arr.tail-i-1] = tem;
}
}
int main()
{
#if 0
ARR arr;
init(&arr);
int n = 10;
while(n--)
arr.addtail(&arr, n);
arr.show(&arr);
// arr.tail = 0;
arr.show(&arr);
#endif
ARR arr;
arr.addtail(1);
arr.addtail(2);
arr.addtail(3);
arr.addtail(4);
arr.show();
//reverse
rev(arr);
arr.show();
}
拷贝构造函数
C++中的拷贝构造函数是一种特殊的构造函数,用于创建一个新对象,该对象是通过复制另一个同类型对象的内容而创建的。
拷贝构造函数的定义如下:
ClassName(const ClassName& obj)
{
// 拷贝构造函数的实现
}
拷贝构造函数的参数是一个同类型的引用,用于指定要复制的对象。在拷贝构造函数中,我们可以通过引用参数访问要复制的对象的成员,并将其复制到新创建的对象中。
拷贝构造函数通常用于以下情况:
- 在创建对象时,希望使用同类型的另一个对象来初始化新对象。
- 将对象作为参数传递给函数,并希望在函数中创建该对象的副本。
如果没有显式定义拷贝构造函数,C++编译器会自动生成一个默认的拷贝构造函数,该函数会将对象的每个成员逐个复制到新对象中。但是,如果对象包含指针或动态分配的内存,这种默认的拷贝构造函数可能会导致浅拷贝问题。因此,当对象包含指针或动态分配的内存时,我们通常需要自定义拷贝构造函数来执行深拷贝操作。
以下是一个使用拷贝构造函数的示例:
#include <iostream>
class MyClass {
public:
int data;
// 默认构造函数
MyClass() : data(0) {}
// 拷贝构造函数
MyClass(const MyClass& obj) : data(obj.data) {}
void display() {
std::cout << "Data: " << data << std::endl;
}
};
int main() {
MyClass obj1;
obj1.data = 10;
MyClass obj2(obj1); // 使用拷贝构造函数创建新对象
obj1.display();
obj2.display();
return 0;
}
在上面的示例中,我们定义了一个名为MyClass
的类,其中包含一个整数成员data
。我们定义了一个默认构造函数和一个拷贝构造函数,然后在main
函数中使用拷贝构造函数创建了一个新对象obj2
,并将obj1
的内容复制到obj2
中。最后,我们调用display
函数来显示obj1
和obj2
的数据。
#include <stdio.h>
#include <string.h>
class A
{
public:
A()
{
printf("A()\n");
p = new char[10];
strcpy(p, "hello");
printf("p: %s\n", p);
printf("p: %s\n", this->p);
}
A(int data)//当已有有参构造函数时,会提醒你建立无参构造函数
{
printf("A(int data)\n");
}
A(const A& x)//拷贝构造函数
{
printf("A(const A &x)\n");
p = new char[10];
strcpy(p, x.p);
}
~A()
{
printf("~A()\n");
delete[] p;
}
private:
char* p;
};
int main()
{
A* p = new A(1000);//调用隐式构造函数
A x;
A y = x;//调用隐式构造函数
// y = x;
}
常成员、常对象(C++推荐const而不用#define, mutable )
!!! const 数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的(static 例外)。
常数据成员(构造函数初始化表赋值)
class A{
public: A():x(100) { }
const int x;
}
常成员函数
void func() const;//函数不能让对象中的数据进行破坏,函数不能进行数据的操作
常对象
const A a;
#include <stdio.h>
class A{
public:
A(int a = 50, int data = 1000):b(data){//前面括号是数据初始化,后面是初始化表
// b = data;
this->a = a;
printf("AAAAAAAAA\n");
}
~A(){
printf("~~~~~~~~~\n");
}
void show(void) const//常函数
{
printf("b = %d\n", b);
printf("a = %d\n", a);
// a++;多一次编译器帮忙找错的机会 常量修饰
// b++;
}
private:
int a;
const int b;
};
int main()
{
A x(10);
x.show();
A y(100);
y.show();
A z;
z.show();
}
静态成员(属于类不属于对象)
静态成员的申明
static int x;
static const int x = 10;
静态数据成员初始化
类外: static int A::x = 10;
静态成员函数
static void func(); //能访问静态成员
调用方法 A::func();
静态成员是指属于类而不是对象的成员。它们在类的定义中被声明,并且在类的所有对象中共享。与普通成员不同,静态成员不依赖于类的任何对象,而是直接与类关联。
静态成员可以是静态变量或静态方法。静态变量是在类的所有对象之间共享的变量,它们只有一个副本存在于内存中。静态方法是不需要通过对象来调用的方法,可以直接通过类名调用。静态方法不能访问非静态成员,因为非静态成员依赖于对象的创建。
理解静态成员的好处包括:
- 节省内存:静态成员只有一个副本,不需要为每个对象分配内存空间。
- 共享数据:静态成员在类的所有对象之间共享,可以用于存储和访问与类相关的全局数据。
- 简化访问:静态方法可以直接通过类名调用,无需创建对象,方便简洁。
需要注意的是,静态成员在类加载时被初始化,在程序运行期间一直存在,可以通过类名直接访问,不需要创建对象。同时,静态成员不能访问非静态成员,因为非静态成员依赖于对象的创建。
#include <stdio.h>
class A
{
public:
static void func(void)//静态函数基于类,不基于对象,如果取消static,A::func()将会报错
{
printf("xxxxxxxxx\n");
}
static int data;
};
int A::data = 10;//静态成员初始化,要写在类外
int main()
{
A a;
a.func();
A::func();
A x;
x.data = 100;
printf("x.data = %d\n", x.data);
A::data = 1000;
printf("x.data = %d\n", x.data);
}
友元(破坏封装)
友元类 friend class B;
友元函数 friend void func();
友元成员函数 friend void B::func();
#include <stdio.h>
class A;
class B
{
public:
void printfA(A& x);
};
class A
{
public:
A()
{
x = 100;
}
// friend class B;//友元类
friend void B::printfA(A& x);//友元成员函数
private:
int x;
};
void B::printfA(A& x)
{
printf("%d\n", x.x);
}
int main()
{
A a;
// printf("%d\n", a.x);
B b;
b.printfA(a);
}
namespace
在C++中,namespace是用于组织代码、避免命名冲突的机制。它可以将相关的变量、函数和类放置在一个逻辑上的分组中。
通过使用namespace
关键字,后面跟上namespace的名称,可以定义一个namespace。例如:
namespace MyNamespace {
// 代码放在这里
}
在namespace中,可以像在namespace之外一样定义变量、函数和类。但是,这些实体将被封装在namespace中,只能使用namespace名称来访问。
要访问namespace中的实体,可以使用::
作用域解析运算符。例如:
MyNamespace::myFunction();
如果想要在不使用作用域解析运算符的情况下使用特定的namespace,可以使用using
指令。例如:
using namespace MyNamespace;
这将允许直接访问namespace中的实体,而无需使用namespace名称。
namespace在避免命名冲突方面非常有用,特别是在处理大型代码库或使用定义了自己实体的库时。通过将相关实体放置在一个namespace中,可以确保它们在该namespace内的名称是唯一的,减少冲突的可能性。
此外,namespace可以嵌套在其他namespace中,进一步组织和分隔代码。